//	VirtualDub - Video processing and capture application
//	A/V interface library
//	Copyright (C) 1998-2006 Avery Lee
//
//	This program is free software; you can redistribute it and/or modify
//	it under the terms of the GNU General Public License as published by
//	the Free Software Foundation; either version 2 of the License, or
//	(at your option) any later version.
//
//	This program is distributed in the hope that it will be useful,
//	but WITHOUT ANY WARRANTY; without even the implied warranty of
//	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//	GNU General Public License for more details.
//
//	You should have received a copy of the GNU General Public License
//	along with this program; if not, write to the Free Software
//	Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

#ifdef _MSC_VER
	#pragma warning(disable: 4786)		// STFU
#endif

#include <vd2/Riza/capdriver.h>
#include <vd2/Riza/opengl.h>
#include <vd2/system/binary.h>
#include <vd2/system/vdstring.h>
#include <vd2/system/error.h>
#include <vd2/system/time.h>
#include <vd2/system/profile.h>
#include <vd2/system/registry.h>
#include <windows.h>
#include <vector>
#include "resource.h"

using namespace nsVDCapture;

extern HINSTANCE g_hInst;

namespace {
	#include "cap_screen_glshaders.inl"

	DWORD AutodetectCaptureBltMode() {
		OSVERSIONINFOA verinfo={sizeof(OSVERSIONINFOA)};

		if (GetVersionEx(&verinfo)) {
			if (verinfo.dwPlatformId == VER_PLATFORM_WIN32_NT) {
				// Windows 2000 or newer
				if (verinfo.dwMajorVersion >= 5)
					return SRCCOPY | CAPTUREBLT;
			} else if (verinfo.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS) {
				// Test for Windows 98 or newer
				if (verinfo.dwMajorVersion >= 5 || (verinfo.dwMajorVersion == 4 && verinfo.dwMinorVersion >= 10))
					return SRCCOPY | CAPTUREBLT;
			}

		}

		return SRCCOPY;
	}
}

class VDCaptureDriverScreen : public IVDCaptureDriver, public IVDTimerCallback {
	VDCaptureDriverScreen(const VDCaptureDriverScreen&);
	VDCaptureDriverScreen& operator=(const VDCaptureDriverScreen&);
public:
	VDCaptureDriverScreen();
	~VDCaptureDriverScreen();

	void	*AsInterface(uint32 id) { return NULL; }

	bool	Init(VDGUIHandle hParent);
	void	Shutdown();

	void	SetCallback(IVDCaptureDriverCallback *pCB);

	void	LockUpdates() {}
	void	UnlockUpdates() {}

	bool	IsHardwareDisplayAvailable();

	void	SetDisplayMode(nsVDCapture::DisplayMode m);
	nsVDCapture::DisplayMode		GetDisplayMode();

	void	SetDisplayRect(const vdrect32& r);
	vdrect32	GetDisplayRectAbsolute();
	void	SetDisplayVisibility(bool vis);

	void	SetFramePeriod(sint32 ms);
	sint32	GetFramePeriod();

	uint32	GetPreviewFrameCount();

	bool	GetVideoFormat(vdstructex<BITMAPINFOHEADER>& vformat);
	bool	SetVideoFormat(const BITMAPINFOHEADER *pbih, uint32 size);

	bool	SetTunerChannel(int channel) { return false; }
	int		GetTunerChannel() { return -1; }
	bool	GetTunerChannelRange(int& minChannel, int& maxChannel) { return false; }
	uint32	GetTunerFrequencyPrecision() { return 0; }
	uint32	GetTunerExactFrequency() { return 0; }
	bool	SetTunerExactFrequency(uint32 freq) { return false; }
	nsVDCapture::TunerInputMode	GetTunerInputMode() { return kTunerInputUnknown; }
	void	SetTunerInputMode(nsVDCapture::TunerInputMode tunerMode) {}

	int		GetAudioDeviceCount();
	const wchar_t *GetAudioDeviceName(int idx);
	bool	SetAudioDevice(int idx);
	int		GetAudioDeviceIndex();
	bool	IsAudioDeviceIntegrated(int idx) { return false; }

	int		GetVideoSourceCount();
	const wchar_t *GetVideoSourceName(int idx);
	bool	SetVideoSource(int idx);
	int		GetVideoSourceIndex();

	int		GetAudioSourceCount();
	const wchar_t *GetAudioSourceName(int idx);
	bool	SetAudioSource(int idx);
	int		GetAudioSourceIndex();

	int		GetAudioInputCount();
	const wchar_t *GetAudioInputName(int idx);
	bool	SetAudioInput(int idx);
	int		GetAudioInputIndex();

	int		GetAudioSourceForVideoSource(int idx) { return -2; }

	bool	IsAudioCapturePossible();
	bool	IsAudioCaptureEnabled();
	bool	IsAudioPlaybackPossible() { return false; }
	bool	IsAudioPlaybackEnabled() { return false; }
	void	SetAudioCaptureEnabled(bool b);
	void	SetAudioAnalysisEnabled(bool b);
	void	SetAudioPlaybackEnabled(bool b) {}

	void	GetAvailableAudioFormats(std::list<vdstructex<WAVEFORMATEX> >& aformats);

	bool	GetAudioFormat(vdstructex<WAVEFORMATEX>& aformat);
	bool	SetAudioFormat(const WAVEFORMATEX *pwfex, uint32 size);

	bool	IsDriverDialogSupported(nsVDCapture::DriverDialog dlg);
	void	DisplayDriverDialog(nsVDCapture::DriverDialog dlg);

	bool	IsPropertySupported(uint32 id) { return false; }
	sint32	GetPropertyInt(uint32 id, bool *pAutomatic) { if (pAutomatic) *pAutomatic = true; return 0; }
	void	SetPropertyInt(uint32 id, sint32 value, bool automatic) {}
	void	GetPropertyInfoInt(uint32 id, sint32& minVal, sint32& maxVal, sint32& step, sint32& defaultVal, bool& automatic, bool& manual) {}

	bool	CaptureStart();
	void	CaptureStop();
	void	CaptureAbort();

protected:
	void	SyncCaptureStop();
	void	SyncCaptureAbort();
	void	InitMixerSupport();
	void	ShutdownMixerSupport();
	bool	InitWaveAnalysis();
	void	ShutdownWaveAnalysis();
	bool	InitVideoBuffer();
	void	ShutdownVideoBuffer();
	void	FlushFrameQueue();
	sint64	ComputeGlobalTime();
	void	DoFrame();
	void	DispatchFrame(const void *data, uint32 size, sint64 timestamp);

	void	TimerCallback();

	void	ConvertToYV12_GL_NV1x(int w, int h, float u, float v);
	void	ConvertToYV12_GL_NV2x(int w, int h, float u, float v);
	void	ConvertToYV12_GL_ATIFS(int w, int h, float u, float v);
	void	ConvertToYUY2_GL_NV1x(int w, int h, float u, float v);
	void	ConvertToYUY2_GL_NV2x_ATIFS(int w, int h, float u, float v, bool atifs);
	GLuint	GetOcclusionQueryPixelCountSafe(GLuint query);

	void	LoadSettings();
	void	SaveSettings();

	static LRESULT CALLBACK StaticWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
	static LRESULT CALLBACK StaticWndProcGL(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
	static INT_PTR CALLBACK VideoSourceDlgProc(HWND hdlg, UINT msg, WPARAM wParam, LPARAM lParam);

	enum { kPreviewTimerID = 100 };

	HWND	mhwndParent;
	HWND	mhwnd;
	HWND	mhwndGL;
	HWND	mhwndGLDraw;

	bool	mbCapBuffersInited;
	HDC		mhdcOffscreen;
	HBITMAP	mhbmOffscreen;
	void	*mpOffscreenData;
	uint32	mOffscreenSize;

	VDOpenGLBinding		mGL;
	bool	mbOpenGLMode;
	GLuint	mGLBuffers[2];
	GLuint	mGLShaderBase;
	GLuint	mGLTextures[2];
	int		mGLTextureW;
	int		mGLTextureH;
	vdblock<uint32> mGLReadBuffer;
	GLuint	mGLOcclusionQueries[2];
	bool	mbFrameValid[2];
	bool	mbGLOcclusionValid[2];
	bool	mbGLOcclusionPrevFrameValid;
	sint64	mTimestampQueue[4];
	int		mTimestampDelay;
	int		mTimestampIndex;

	GLuint	mGLCursorCacheTexture;
	float	mGLCursorCacheTextureInvW;
	float	mGLCursorCacheTextureInvH;
	HCURSOR	mCachedCursor;
	int		mCachedCursorWidth;
	int		mCachedCursorHeight;
	int		mCachedCursorHotspotX;
	int		mCachedCursorHotspotY;
	bool	mbCachedCursorXORMode;

	HDC		mhdcCursorBuffer;
	HBITMAP	mhbmCursorBuffer;
	HGDIOBJ	mhbmCursorBufferOld;
	uint32	*mpCursorBuffer;

	VDAtomicInt	mbCaptureFramePending;
	bool	mbCapturing;
	bool	mbCaptureSetup;
	bool	mbVisible;
	bool	mbAudioHardwarePresent;
	bool	mbAudioHardwareEnabled;
	bool	mbAudioCaptureEnabled;
	bool	mbAudioAnalysisEnabled;
	bool	mbAudioAnalysisActive;

	bool	mbTrackCursor;
	bool	mbTrackActiveWindow;
	bool	mbTrackActiveWindowClient;
	int		mTrackX;
	int		mTrackY;
	int		mTrackOffsetX;
	int		mTrackOffsetY;

	bool	mbRescaleImage;
	int		mRescaleW;
	int		mRescaleH;

	bool	mbDrawMousePointer;
	bool	mbRemoveDuplicates;

	uint32	mFramePeriod;

	vdstructex<BITMAPINFOHEADER>	mVideoFormat;
	vdstructex<WAVEFORMATEX>		mAudioFormat;

	IVDCaptureDriverCallback	*mpCB;
	DisplayMode			mDisplayMode;
	uint32	mGlobalTimeBase;

	UINT	mPreviewFrameTimer;
	VDAtomicInt	mPreviewFrameCount;

	VDCallbackTimer	mCaptureTimer;

	HMIXER	mhMixer;
	int		mMixerInput;
	MIXERCONTROL	mMixerInputControl;
	typedef std::vector<VDStringW>	MixerInputs;
	MixerInputs	mMixerInputs;

	HWAVEIN mhWaveIn;
	WAVEHDR	mWaveBufHdrs[2];
	vdblock<char>	mWaveBuffer;

	MyError	mCaptureError;

	VDRTProfileChannel mProfileChannel;

	static ATOM sWndClass;
	static ATOM sWndClassGL;
};

ATOM VDCaptureDriverScreen::sWndClass;
ATOM VDCaptureDriverScreen::sWndClassGL;

VDCaptureDriverScreen::VDCaptureDriverScreen()
	: mhwndParent(NULL)
	, mhwnd(NULL)
	, mhwndGL(NULL)
	, mhwndGLDraw(NULL)
	, mbCapBuffersInited(false)
	, mhdcOffscreen(NULL)
	, mhbmOffscreen(NULL)
	, mpOffscreenData(NULL)
	, mOffscreenSize(0)
	, mbOpenGLMode(true)
	, mGLShaderBase(0)
	, mGLTextureW(1)
	, mGLTextureH(1)
	, mGLCursorCacheTexture(0)
	, mCachedCursor(NULL)
	, mCachedCursorHotspotX(0)
	, mCachedCursorHotspotY(0)
	, mhdcCursorBuffer(NULL)
	, mhbmCursorBuffer(NULL)
	, mhbmCursorBufferOld(NULL)
	, mpCursorBuffer(NULL)
	, mbCaptureFramePending(false)
	, mbCapturing(false)
	, mbCaptureSetup(false)
	, mbVisible(false)
	, mbAudioAnalysisEnabled(false)
	, mbAudioAnalysisActive(false)
	, mbTrackCursor(false)
	, mbTrackActiveWindow(false)
	, mbTrackActiveWindowClient(false)
	, mTrackX(0)
	, mTrackY(0)
	, mTrackOffsetX(0)
	, mTrackOffsetY(0)
	, mbRescaleImage(false)
	, mRescaleW(GetSystemMetrics(SM_CXSCREEN))
	, mRescaleH(GetSystemMetrics(SM_CYSCREEN))
	, mbDrawMousePointer(true)
	, mbRemoveDuplicates(true)
	, mFramePeriod(10000000 / 30)
	, mpCB(NULL)
	, mDisplayMode(kDisplayNone)
	, mPreviewFrameTimer(0)
	, mhMixer(NULL)
	, mhWaveIn(NULL)
	, mProfileChannel("Capture driver")
{
	memset(mWaveBufHdrs, 0, sizeof mWaveBufHdrs);

	mVideoFormat.resize(sizeof(BITMAPINFOHEADER));
	mVideoFormat->biSize		= sizeof(BITMAPINFOHEADER);
	mVideoFormat->biWidth		= 320;
	mVideoFormat->biHeight		= 240;
	mVideoFormat->biPlanes		= 1;
	mVideoFormat->biCompression	= BI_RGB;
	mVideoFormat->biBitCount	= 32;
	mVideoFormat->biSizeImage	= 320*240*4;
	mVideoFormat->biXPelsPerMeter	= 0;
	mVideoFormat->biYPelsPerMeter	= 0;
	mVideoFormat->biClrUsed			= 0;
	mVideoFormat->biClrImportant	= 0;

	mAudioFormat.resize(sizeof(WAVEFORMATEX));
	mAudioFormat->wFormatTag		= WAVE_FORMAT_PCM;
	mAudioFormat->nSamplesPerSec	= 44100;
	mAudioFormat->nAvgBytesPerSec	= 44100*4;
	mAudioFormat->nChannels			= 2;
	mAudioFormat->nBlockAlign		= 4;
	mAudioFormat->wBitsPerSample	= 16;
	mAudioFormat->cbSize			= 0;

	mOffscreenSize = mVideoFormat->biSizeImage;

	mGLOcclusionQueries[0] = 0;
	mGLOcclusionQueries[1] = 0;
	mbGLOcclusionValid[0] = false;
	mbGLOcclusionValid[1] = false;
	mbGLOcclusionPrevFrameValid = false;
}

VDCaptureDriverScreen::~VDCaptureDriverScreen() {
	Shutdown();
}

void VDCaptureDriverScreen::SetCallback(IVDCaptureDriverCallback *pCB) {
	mpCB = pCB;
}

bool VDCaptureDriverScreen::Init(VDGUIHandle hParent) {
	mhwndParent = (HWND)hParent;

	if (!sWndClass) {
		WNDCLASS wc = { 0, StaticWndProc, 0, sizeof(VDCaptureDriverScreen *), g_hInst, NULL, NULL, NULL, NULL, "Riza screencap window" };

		sWndClass = RegisterClass(&wc);

		if (!sWndClass)
			return false;

		WNDCLASS wcgl = { CS_OWNDC, StaticWndProcGL, 0, sizeof(VDCaptureDriverScreen *), g_hInst, NULL, NULL, NULL, NULL, "Riza screencap GL window" };

		sWndClassGL = RegisterClass(&wcgl);

		if (!sWndClassGL)
			return false;
	}

	// Attempt to open mixer device. It is OK for this to fail. Note that we
	// have a bit of a problem in that (a) the mixer API doesn't take
	// WAVE_MAPPER, and (b) we can't get access to the handle that the
	// capture window creates. For now, we sort of fake it.
	InitMixerSupport();

	// Create message sink.
	if (!(mhwnd = CreateWindow((LPCTSTR)sWndClass, "", WS_CHILD, 0, 0, 0, 0, mhwndParent, NULL, g_hInst, this))) {
		Shutdown();
		return false;
	}

	mPreviewFrameCount = 0;

	mbAudioHardwarePresent = false;
	mbAudioHardwarePresent = true;
	mbAudioHardwareEnabled = mbAudioHardwarePresent;
	mbAudioCaptureEnabled = true;

	LoadSettings();

	InitVideoBuffer();

	return true;
}

void VDCaptureDriverScreen::Shutdown() {
	ShutdownWaveAnalysis();

	if (mPreviewFrameTimer) {
		KillTimer(mhwnd, mPreviewFrameTimer);
		mPreviewFrameTimer = 0;
	}

	SaveSettings();

	if (mhwnd) {
		DestroyWindow(mhwnd);
		mhwnd = NULL;
	}

	if (mhMixer) {
		mixerClose(mhMixer);
		mhMixer = NULL;
	}
}

bool VDCaptureDriverScreen::IsHardwareDisplayAvailable() {
	return true;
}

void VDCaptureDriverScreen::SetDisplayMode(DisplayMode mode) {
	if (mode == mDisplayMode)
		return;

	if (mDisplayMode == kDisplayAnalyze) {
		if (mPreviewFrameTimer) {
			KillTimer(mhwnd, mPreviewFrameTimer);
			mPreviewFrameTimer = 0;
		}
	}

	mDisplayMode = mode;

	if (mPreviewFrameTimer) {
		KillTimer(mhwnd, mPreviewFrameTimer);
		mPreviewFrameTimer = 0;
	}

	switch(mode) {
	case kDisplayNone:
		ShowWindow(mhwnd, SW_HIDE);
		break;
	case kDisplayHardware:
		mPreviewFrameTimer = SetTimer(mhwnd, kPreviewTimerID, mFramePeriod / 10000, NULL);

		if (mbVisible)
			ShowWindow(mhwnd, SW_SHOWNA);
		break;
	case kDisplaySoftware:
		mPreviewFrameTimer = SetTimer(mhwnd, kPreviewTimerID, mFramePeriod / 10000, NULL);

		if (mbVisible)
			ShowWindow(mhwnd, SW_SHOWNA);
		break;
	case kDisplayAnalyze:
		mPreviewFrameTimer = SetTimer(mhwnd, kPreviewTimerID, mFramePeriod / 10000, NULL);

		if (mbVisible)
			ShowWindow(mhwnd, SW_HIDE);
		break;
	}
}

DisplayMode VDCaptureDriverScreen::GetDisplayMode() {
	return mDisplayMode;
}

void VDCaptureDriverScreen::SetDisplayRect(const vdrect32& r) {
	SetWindowPos(mhwnd, NULL, r.left, r.top, r.width(), r.height(), SWP_NOZORDER|SWP_NOACTIVATE);
}

vdrect32 VDCaptureDriverScreen::GetDisplayRectAbsolute() {
	RECT r;
	GetWindowRect(mhwnd, &r);
	MapWindowPoints(GetParent(mhwnd), NULL, (LPPOINT)&r, 2);
	return vdrect32(r.left, r.top, r.right, r.bottom);
}

void VDCaptureDriverScreen::SetDisplayVisibility(bool vis) {
	if (vis == mbVisible)
		return;

	mbVisible = vis;
	ShowWindow(mhwnd, vis && mDisplayMode != kDisplayAnalyze ? SW_SHOWNA : SW_HIDE);
}

void VDCaptureDriverScreen::SetFramePeriod(sint32 ms) {
	mFramePeriod = ms;

	if (mpCB)
		mpCB->CapEvent(kEventVideoFrameRateChanged, 0);
}

sint32 VDCaptureDriverScreen::GetFramePeriod() {
	return mFramePeriod;
}

uint32 VDCaptureDriverScreen::GetPreviewFrameCount() {
	if (mDisplayMode == kDisplaySoftware || mDisplayMode == kDisplayAnalyze)
		return mPreviewFrameCount;

	return 0;
}

bool VDCaptureDriverScreen::GetVideoFormat(vdstructex<BITMAPINFOHEADER>& vformat) {
	vformat = mVideoFormat;
	return true;
}

bool VDCaptureDriverScreen::SetVideoFormat(const BITMAPINFOHEADER *pbih, uint32 size) {
	bool success = false;

	if (pbih->biCompression == VDMAKEFOURCC('Y', 'U', 'Y', '2')) {
		if (!mbOpenGLMode)
			return false;

		if ((pbih->biWidth & 1))
			return false;

		mOffscreenSize = pbih->biWidth * abs(pbih->biHeight) * 2;
	} else if (pbih->biCompression == VDMAKEFOURCC('Y', 'V', '1', '2')) {
		if (!mbOpenGLMode)
			return false;

		if ((pbih->biWidth & 7) || (pbih->biHeight & 1))
			return false;
		mOffscreenSize = pbih->biWidth * abs(pbih->biHeight) * 3 / 2;
	} else if (pbih->biCompression == BI_RGB) {
		if (pbih->biBitCount != 32)
			return false;

		mOffscreenSize = pbih->biWidth * abs(pbih->biHeight) * 4;
	} else {
		return false;
	}

	ShutdownVideoBuffer();
	mVideoFormat.assign(pbih, size);
	if (mpCB)
		mpCB->CapEvent(kEventVideoFormatChanged, 0);
	InitVideoBuffer();
	success = true;
	return success;
}

int VDCaptureDriverScreen::GetAudioDeviceCount() {
	return mbAudioHardwarePresent ? 1 : 0;
}

const wchar_t *VDCaptureDriverScreen::GetAudioDeviceName(int idx) {
	if (idx || !mbAudioHardwarePresent)
		return NULL;

	return L"Wave Mapper";
}

bool VDCaptureDriverScreen::SetAudioDevice(int idx) {
	if (idx < -1 || idx >= 1)
		return false;
	
	if (!idx && !mbAudioHardwarePresent)
		return false;

	bool enable = !idx;

	if (enable == mbAudioHardwareEnabled)
		return true;

	ShutdownWaveAnalysis();
	mbAudioHardwareEnabled = enable;
	InitWaveAnalysis();

	return true;
}

int VDCaptureDriverScreen::GetAudioDeviceIndex() {
	return mbAudioHardwareEnabled ? 0 : -1;
}

int VDCaptureDriverScreen::GetVideoSourceCount() {
	return 0;
}

const wchar_t *VDCaptureDriverScreen::GetVideoSourceName(int idx) {
	return NULL;
}

bool VDCaptureDriverScreen::SetVideoSource(int idx) {
	return idx == -1;
}

int VDCaptureDriverScreen::GetVideoSourceIndex() {
	return -1;
}

int VDCaptureDriverScreen::GetAudioSourceCount() {
	return 0;
}

const wchar_t *VDCaptureDriverScreen::GetAudioSourceName(int idx) {
	return NULL;
}

bool VDCaptureDriverScreen::SetAudioSource(int idx) {
	return idx == -1;
}

int VDCaptureDriverScreen::GetAudioSourceIndex() {
	return -1;
}

int VDCaptureDriverScreen::GetAudioInputCount() {
	return mbAudioHardwareEnabled ? mMixerInputs.size() : 0;
}

const wchar_t *VDCaptureDriverScreen::GetAudioInputName(int idx) {
	if (!mbAudioHardwareEnabled || (unsigned)idx >= mMixerInputs.size())
		return NULL;

	MixerInputs::const_iterator it(mMixerInputs.begin());

	std::advance(it, idx);

	return (*it).c_str();
}

bool VDCaptureDriverScreen::SetAudioInput(int idx) {
	if (!mbAudioHardwareEnabled || !mhMixer)
		return idx == -1;

	VDASSERT(mMixerInputs.size() == mMixerInputControl.cMultipleItems);

	if (idx != -1 && (unsigned)idx >= mMixerInputControl.cMultipleItems)
		return false;

	// attempt to set the appropriate mixer input

	vdblock<MIXERCONTROLDETAILS_BOOLEAN> vals(mMixerInputControl.cMultipleItems);

	for(unsigned i=0; i<mMixerInputControl.cMultipleItems; ++i)
		vals[i].fValue = (i == idx);

	MIXERCONTROLDETAILS details = {sizeof(MIXERCONTROLDETAILS)};

	details.dwControlID		= mMixerInputControl.dwControlID;
	details.cChannels		= 1;
	details.cMultipleItems	= mMixerInputControl.cMultipleItems;
	details.cbDetails		= sizeof(MIXERCONTROLDETAILS_BOOLEAN);
	details.paDetails		= vals.data();

	if (MMSYSERR_NOERROR != mixerSetControlDetails((HMIXEROBJ)mhMixer, &details, MIXER_SETCONTROLDETAILSF_VALUE))
		return false;

	mMixerInput = idx;

	return true;
}

int VDCaptureDriverScreen::GetAudioInputIndex() {
	return mbAudioHardwareEnabled ? mMixerInput : -1;
}

bool VDCaptureDriverScreen::IsAudioCapturePossible() {
	return mbAudioHardwareEnabled;
}

bool VDCaptureDriverScreen::IsAudioCaptureEnabled() {
	return mbAudioHardwareEnabled && mbAudioCaptureEnabled;
}

void VDCaptureDriverScreen::SetAudioCaptureEnabled(bool b) {
	mbAudioCaptureEnabled = b;
}

void VDCaptureDriverScreen::SetAudioAnalysisEnabled(bool b) {
	if (mbAudioAnalysisEnabled == b)
		return;

	mbAudioAnalysisEnabled = b;

	if (mbAudioAnalysisEnabled)
		InitWaveAnalysis();
	else
		ShutdownWaveAnalysis();
}

void VDCaptureDriverScreen::GetAvailableAudioFormats(std::list<vdstructex<WAVEFORMATEX> >& aformats) {
	static const int kSamplingRates[]={
		8000,
		11025,
		12000,
		16000,
		22050,
		24000,
		32000,
		44100,
		48000,
		96000,
		192000
	};

	static const int kChannelCounts[]={
		1,
		2
	};

	static const int kSampleDepths[]={
		8,
		16
	};

	for(int sridx=0; sridx < sizeof kSamplingRates / sizeof kSamplingRates[0]; ++sridx)
		for(int chidx=0; chidx < sizeof kChannelCounts / sizeof kChannelCounts[0]; ++chidx)
			for(int sdidx=0; sdidx < sizeof kSampleDepths / sizeof kSampleDepths[0]; ++sdidx) {
				WAVEFORMATEX wfex={
					WAVE_FORMAT_PCM,
					kChannelCounts[chidx],
					kSamplingRates[sridx],
					0,
					0,
					kSampleDepths[sdidx],
					0
				};

				wfex.nBlockAlign = wfex.nChannels * (wfex.wBitsPerSample >> 3);
				wfex.nAvgBytesPerSec = wfex.nBlockAlign * wfex.nSamplesPerSec;

				if (MMSYSERR_NOERROR ==waveInOpen(NULL, WAVE_MAPPER, &wfex, 0, 0, WAVE_FORMAT_QUERY | WAVE_FORMAT_DIRECT)) {
					aformats.push_back(vdstructex<WAVEFORMATEX>(&wfex, sizeof wfex));
				}
			}
}

bool VDCaptureDriverScreen::GetAudioFormat(vdstructex<WAVEFORMATEX>& aformat) {
	aformat = mAudioFormat;
	return true;
}

bool VDCaptureDriverScreen::SetAudioFormat(const WAVEFORMATEX *pwfex, uint32 size) {
	if (!mbAudioHardwareEnabled)
		return false;

	ShutdownWaveAnalysis();
	mAudioFormat.assign(pwfex, size);
	if (mbAudioAnalysisEnabled)
		InitWaveAnalysis();
	return true;
}

bool VDCaptureDriverScreen::IsDriverDialogSupported(DriverDialog dlg) {
	if (dlg == kDialogVideoSource)
		return true;

	return false;
}

void VDCaptureDriverScreen::DisplayDriverDialog(DriverDialog dlg) {
	VDASSERT(IsDriverDialogSupported(dlg));

	switch(dlg) {
	case kDialogVideoFormat:
		break;
	case kDialogVideoSource:
		ShutdownVideoBuffer();
		DialogBoxParam(g_hInst, MAKEINTRESOURCE(IDD_SCREENCAP_OPTS), mhwnd, VideoSourceDlgProc, (LONG_PTR)this);

		SaveSettings();

		// without OpenGL, we can only do 32-bit RGB
		if (!mbOpenGLMode && mVideoFormat->biCompression != BI_RGB) {
			mVideoFormat->biCompression = BI_RGB;
			mVideoFormat->biSizeImage = mVideoFormat->biWidth * abs(mVideoFormat->biHeight) * 4;
			mVideoFormat->biBitCount = 32;
			mpCB->CapEvent(kEventVideoFormatChanged, 0);
		}

		InitVideoBuffer();
		break;
	case kDialogVideoDisplay:
		break;
	}
}

bool VDCaptureDriverScreen::CaptureStart() {
	ShutdownWaveAnalysis();

	if (!VDINLINEASSERTFALSE(mbCapturing)) {
		FlushFrameQueue();

		if (mpCB->CapEvent(kEventPreroll, 0)) {
			mpCB->CapBegin(0);
			if (mbAudioCaptureEnabled)
				InitWaveAnalysis();

			mGlobalTimeBase = VDGetAccurateTick();
			mbCapturing = true;
			mbCaptureSetup = true;
			mCaptureTimer.Init2(this, mFramePeriod);
		} else {
			if (mbAudioAnalysisEnabled)
				InitWaveAnalysis();
		}
	}

	return mbCapturing;
}

void VDCaptureDriverScreen::CaptureStop() {
	SendMessage(mhwnd, WM_APP+16, 0, 0);
}

void VDCaptureDriverScreen::CaptureAbort() {
	SendMessage(mhwnd, WM_APP+17, 0, 0);
	mbCapturing = false;
}

void VDCaptureDriverScreen::SyncCaptureStop() {
	if (VDINLINEASSERT(mbCaptureSetup)) {
		mCaptureTimer.Shutdown();
		mbCapturing = false;
		mbCaptureSetup = false;

		if (mCaptureError.gets()) {
			mpCB->CapEnd(&mCaptureError);
			mCaptureError.discard();
		} else
			mpCB->CapEnd(NULL);

		if (mbAudioCaptureEnabled)
			ShutdownWaveAnalysis();

		if (mbAudioAnalysisEnabled)
			InitWaveAnalysis();
	}
}

void VDCaptureDriverScreen::SyncCaptureAbort() {
	SyncCaptureStop();
}

void VDCaptureDriverScreen::InitMixerSupport() {
	WAVEINCAPS wcaps={0};
	if (MMSYSERR_NOERROR == waveInGetDevCaps(WAVE_MAPPER, &wcaps, sizeof wcaps) && wcaps.dwFormats) {
		WAVEFORMATEX wfex;

		// create lowest-common denominator format for device
		wfex.wFormatTag			= WAVE_FORMAT_PCM;

		if (wcaps.dwFormats & (WAVE_FORMAT_4M08 | WAVE_FORMAT_4M16 | WAVE_FORMAT_4S08 | WAVE_FORMAT_4S16))
			wfex.nSamplesPerSec = 11025;
		else if (wcaps.dwFormats & (WAVE_FORMAT_2M08 | WAVE_FORMAT_2M16 | WAVE_FORMAT_2S08 | WAVE_FORMAT_2S16))
			wfex.nSamplesPerSec = 22050;
		else
			wfex.nSamplesPerSec = 44100;

		if (wcaps.dwFormats & (WAVE_FORMAT_1M08 | WAVE_FORMAT_1M16 | WAVE_FORMAT_2M08 | WAVE_FORMAT_2M16 | WAVE_FORMAT_4M08 | WAVE_FORMAT_4M16))
			wfex.nChannels = 1;
		else
			wfex.nChannels = 2;

		if (wcaps.dwFormats & (WAVE_FORMAT_1M08 | WAVE_FORMAT_1S08 | WAVE_FORMAT_2M08 | WAVE_FORMAT_2S08 | WAVE_FORMAT_4M08 | WAVE_FORMAT_4S08))
			wfex.wBitsPerSample = 8;
		else
			wfex.wBitsPerSample = 16;

		wfex.nBlockAlign		= wfex.wBitsPerSample >> 3;
		wfex.nAvgBytesPerSec	= wfex.nSamplesPerSec * wfex.nBlockAlign;
		wfex.cbSize				= 0;

		// create the device
		HWAVEIN hwi;
		if (MMSYSERR_NOERROR == waveInOpen(&hwi, WAVE_MAPPER, &wfex, 0, 0, CALLBACK_NULL)) {
			// create mixer based on device

			if (MMSYSERR_NOERROR == mixerOpen(&mhMixer, (UINT)hwi, 0, 0, MIXER_OBJECTF_HWAVEIN)) {
				MIXERLINE mixerLine = {sizeof(MIXERLINE)};

				mixerLine.dwComponentType = MIXERLINE_COMPONENTTYPE_DST_WAVEIN;

				if (MMSYSERR_NOERROR == mixerGetLineInfo((HMIXEROBJ)mhMixer, &mixerLine, MIXER_GETLINEINFOF_COMPONENTTYPE)) {

					// Try to find a MIXER or MUX control
					MIXERLINECONTROLS lineControls = {sizeof(MIXERLINECONTROLS)};

					mMixerInputControl.cbStruct = sizeof(MIXERCONTROL);
					mMixerInputControl.dwControlType = 0;

					lineControls.dwLineID = mixerLine.dwLineID;
					lineControls.dwControlType = MIXERCONTROL_CONTROLTYPE_MUX;
					lineControls.cControls = 1;
					lineControls.pamxctrl = &mMixerInputControl;
					lineControls.cbmxctrl = sizeof(MIXERCONTROL);

					MMRESULT res;

					res = mixerGetLineControls((HMIXEROBJ)mhMixer, &lineControls, MIXER_GETLINECONTROLSF_ONEBYTYPE);

					if (MMSYSERR_NOERROR != res) {
						lineControls.dwControlType = MIXERCONTROL_CONTROLTYPE_MIXER;

						res = mixerGetLineControls((HMIXEROBJ)mhMixer, &lineControls, MIXER_GETLINECONTROLSF_ONEBYTYPE);
					}

					// The mux/mixer control must be of MULTIPLE type; otherwise, we reject it.
					if (!(mMixerInputControl.fdwControl & MIXERCONTROL_CONTROLF_MULTIPLE))
						res = MMSYSERR_ERROR;

					// If we were successful, then enumerate all source lines and push them into the map.
					if (MMSYSERR_NOERROR != res) {
						mixerClose(mhMixer);
						mhMixer = NULL;
					} else {
						// Enumerate control inputs and populate the name array
						vdblock<MIXERCONTROLDETAILS_LISTTEXT> names(mMixerInputControl.cMultipleItems);

						MIXERCONTROLDETAILS details = {sizeof(MIXERCONTROLDETAILS)};

						details.dwControlID		= mMixerInputControl.dwControlID;
						details.cChannels		= 1;
						details.cMultipleItems	= mMixerInputControl.cMultipleItems;
						details.cbDetails		= sizeof(MIXERCONTROLDETAILS_LISTTEXT);
						details.paDetails		= names.data();

						mMixerInput = -1;

						if (MMSYSERR_NOERROR == mixerGetControlDetails((HMIXEROBJ)mhMixer, &details, MIXER_GETCONTROLDETAILSF_LISTTEXT)) {
							mMixerInputs.reserve(details.cMultipleItems);

							for(unsigned i=0; i<details.cMultipleItems; ++i)
								mMixerInputs.push_back(MixerInputs::value_type(VDTextAToW(names[i].szName)));

							vdblock<MIXERCONTROLDETAILS_BOOLEAN> vals(mMixerInputControl.cMultipleItems);

							details.cbDetails = sizeof(MIXERCONTROLDETAILS_BOOLEAN);
							details.paDetails = vals.data();

							if (MMSYSERR_NOERROR == mixerGetControlDetails((HMIXEROBJ)mhMixer, &details, MIXER_GETCONTROLDETAILSF_VALUE)) {
								// Attempt to find a mixer input that is set. Note that for
								// a multiple-select type (MIXER) this will pick the first
								// enabled input.
								for(unsigned i=0; i<details.cMultipleItems; ++i)
									if (vals[i].fValue) {
										mMixerInput = i;
										break;
									}
							}
						}
					}
				}

				// We don't close the mixer here; it is left open while we have the
				// capture device opened.
			}

			waveInClose(hwi);
		}
	}
}

void VDCaptureDriverScreen::ShutdownMixerSupport() {
	if (mhMixer) {
		mixerClose(mhMixer);
		mhMixer = NULL;
	}
}

bool VDCaptureDriverScreen::InitWaveAnalysis() {
	if (!mbAudioHardwareEnabled)
		return false;

	vdstructex<WAVEFORMATEX> aformat;

	if (!GetAudioFormat(aformat))
		return false;

	uint32	blockSize = (aformat->nAvgBytesPerSec + 9) / 10 + aformat->nBlockAlign - 1;
	blockSize -= blockSize % aformat->nBlockAlign;

	mWaveBuffer.resize(blockSize*2);

	if (MMSYSERR_NOERROR != waveInOpen(&mhWaveIn, WAVE_MAPPER, aformat.data(), (DWORD_PTR)mhwnd, 0, CALLBACK_WINDOW | WAVE_FORMAT_DIRECT))
		return false;

	mbAudioAnalysisActive = true;
	for(int i=0; i<2; ++i) {
		WAVEHDR& hdr = mWaveBufHdrs[i];

		hdr.lpData			= &mWaveBuffer[blockSize*i];
		hdr.dwBufferLength	= blockSize;
		hdr.dwBytesRecorded	= 0;
		hdr.dwFlags			= 0;
		hdr.dwLoops			= 0;
		if (MMSYSERR_NOERROR != waveInPrepareHeader(mhWaveIn, &hdr, sizeof(WAVEHDR))) {
			ShutdownWaveAnalysis();
			return false;
		}

		if (MMSYSERR_NOERROR != waveInAddBuffer(mhWaveIn, &hdr, sizeof(WAVEHDR))) {
			ShutdownWaveAnalysis();
			return false;
		}
	}

	if (MMSYSERR_NOERROR != waveInStart(mhWaveIn)) {
		ShutdownWaveAnalysis();
		return false;
	}

	return true;
}

void VDCaptureDriverScreen::ShutdownWaveAnalysis() {
	if (mhWaveIn) {
		mbAudioAnalysisActive = false;
		waveInReset(mhWaveIn);

		for(int i=0; i<2; ++i) {
			if (mWaveBufHdrs[i].dwFlags & WHDR_PREPARED)
				waveInUnprepareHeader(mhWaveIn, &mWaveBufHdrs[i], sizeof(WAVEHDR));
		}

		waveInClose(mhWaveIn);
		mhWaveIn = NULL;
	}

	mWaveBuffer.clear();
}

bool VDCaptureDriverScreen::InitVideoBuffer() {
	ShutdownVideoBuffer();

	if (!mbOpenGLMode) {
		HDC hdc = GetDC(NULL);
		if (!hdc)
			return false;

		mhdcOffscreen = CreateCompatibleDC(hdc);
		mhbmOffscreen = CreateDIBSection(hdc, (const BITMAPINFO *)mVideoFormat.data(), DIB_PAL_COLORS, &mpOffscreenData, NULL, 0);
		ReleaseDC(NULL, hdc);

		if (!mhbmOffscreen || !mhdcOffscreen) {
			ShutdownVideoBuffer();
			return false;
		}

		DeleteObject(SelectObject(mhdcOffscreen, mhbmOffscreen));

		mOffscreenSize = mVideoFormat->biSizeImage;
	} else {
		if (!mGL.Init()) {
			mbOpenGLMode = false;
			return InitVideoBuffer();
		}

		if (!(mhwndGL = CreateWindow((LPCTSTR)sWndClassGL, "VirtualDub OpenGL support", WS_POPUP, 0, 0, GetSystemMetrics(SM_CXSCREEN), GetSystemMetrics(SM_CYSCREEN), NULL, NULL, g_hInst, this))) {
			mbOpenGLMode = false;
			return InitVideoBuffer();
		}

		RECT r;
		GetClientRect(mhwnd, &r);
		if (!(mhwndGLDraw = CreateWindow((LPCTSTR)sWndClassGL, "VirtualDub OpenGL support", WS_CHILD|WS_VISIBLE, 0, 0, r.right, r.bottom, mhwnd, NULL, g_hInst, this))) {
			mbOpenGLMode = false;
			return InitVideoBuffer();
		}

		HDC hdc = GetDC(mhwndGL);
		if (!mGL.Attach(hdc, 24, 8, 0, 0, true)) {
			ReleaseDC(mhwndGL, hdc);
			mbOpenGLMode = false;
			return InitVideoBuffer();
		}

		HDC hdc2 = GetDC(mhwndGLDraw);
		mGL.AttachAux(hdc2, 24, 8, 0, 0, true);
		ReleaseDC(mhwndGLDraw, hdc2);

		int buffersize = mVideoFormat->biWidth * abs(mVideoFormat->biHeight) * 4;

		mGL.Begin(hdc);

		if (mGL.EXT_pixel_buffer_object) {
			mGL.glGenBuffersARB(2, mGLBuffers);
			mGL.glBindBufferARB(GL_PIXEL_PACK_BUFFER_ARB, mGLBuffers[0]);
			mGL.glBufferDataARB(GL_PIXEL_PACK_BUFFER_ARB, buffersize, NULL, GL_STREAM_READ_ARB);
			mGL.glBindBufferARB(GL_PIXEL_PACK_BUFFER_ARB, mGLBuffers[1]);
			mGL.glBufferDataARB(GL_PIXEL_PACK_BUFFER_ARB, buffersize, NULL, GL_STREAM_READ_ARB);
		} else {
			mGLReadBuffer.resize((buffersize + 3) >> 2);
		}

		VDASSERT(!mGL.glGetError());

		mGL.glGenTextures(2, mGLTextures);
		mGL.glBindTexture(GL_TEXTURE_2D, mGLTextures[0]);

		int srcw = mVideoFormat->biWidth;
		int srch = abs(mVideoFormat->biHeight);

		if (mbRescaleImage) {
			if (srcw < mRescaleW)
				srcw = mRescaleW;
			if (srch < mRescaleH)
				srch = mRescaleH;
		}

		int w = srcw * 2 - 1;
		int h = srch * 2 - 1;

		while(int t = w & (w-1))
			w = t;

		while(int t = h & (h-1))
			h = t;

		mGLTextureW = w;
		mGLTextureH = h;

		mGL.glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8, w, h, 0, GL_BGRA_EXT, GL_UNSIGNED_BYTE, NULL);
		VDASSERT(!mGL.glGetError());

		mGL.glBindTexture(GL_TEXTURE_2D, mGLTextures[1]);
		mGL.glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8, w, h, 0, GL_BGRA_EXT, GL_UNSIGNED_BYTE, NULL);
		VDASSERT(!mGL.glGetError());

		int cxcursor = GetSystemMetrics(SM_CXCURSOR);
		int cycursor = GetSystemMetrics(SM_CYCURSOR);

		mCachedCursorWidth = cxcursor;
		mCachedCursorHeight = cycursor;

		HDC hdcScreen = GetDC(NULL);
		mhdcCursorBuffer = CreateCompatibleDC(hdcScreen);
		const BITMAPINFOHEADER bihCursor={
			sizeof(BITMAPINFOHEADER),
			cxcursor,
			cycursor * 2,
			1,
			32,
			BI_RGB,
			0,
			0,
			0,
			0,
			0
		};
		ReleaseDC(NULL, hdcScreen);

		mhbmCursorBuffer = CreateDIBSection(mhdcCursorBuffer, (const BITMAPINFO *)&bihCursor, DIB_RGB_COLORS, (void **)&mpCursorBuffer, NULL, 0);
		mhbmCursorBufferOld = SelectObject(mhdcCursorBuffer, mhbmCursorBuffer);

		cxcursor = cxcursor * 2 - 1;
		while(int t = cxcursor & (cxcursor - 1))
			cxcursor = t;

		cycursor = cycursor * 2 - 1;
		while(int t = cycursor & (cycursor - 1))
			cycursor = t;

		mCachedCursor = NULL;
		mGLCursorCacheTextureInvW = 1.0f / (float)cxcursor;
		mGLCursorCacheTextureInvH = 1.0f / (float)cycursor;

		mGL.glGenTextures(1, &mGLCursorCacheTexture);
		mGL.glBindTexture(GL_TEXTURE_2D, mGLCursorCacheTexture);
		mGL.glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, cxcursor, cycursor, 0, GL_BGRA_EXT, GL_UNSIGNED_BYTE, NULL);

		if (mGL.NV_occlusion_query && mGL.ARB_multitexture && mbRemoveDuplicates)
			mGL.glGenOcclusionQueriesNV(2, mGLOcclusionQueries);

		// initialize shaders
		mGLShaderBase = mGL.InitTechniques(g_techniques, sizeof g_techniques / sizeof g_techniques[0]);

		VDASSERT(!mGL.glGetError());

		mGL.End();

		ReleaseDC(mhwndGL, hdc);

		mbOpenGLMode = true;

		FlushFrameQueue();

		mTimestampIndex = 0;
		mTimestampDelay = 0;
		if (mGL.EXT_pixel_buffer_object)
			++mTimestampDelay;
		if (mGL.NV_occlusion_query && mGL.ARB_multitexture && mbRemoveDuplicates)
			++mTimestampDelay;
	}

	mbCapBuffersInited = true;
	return true;
}

void VDCaptureDriverScreen::ShutdownVideoBuffer() {
	mbCapBuffersInited = false;

	if (mhbmCursorBufferOld) {
		SelectObject(mhdcCursorBuffer, mhbmCursorBufferOld);
		mhbmCursorBufferOld = NULL;
	}

	if (mhbmCursorBuffer) {
		DeleteObject(mhbmCursorBuffer);
		mhbmCursorBuffer = NULL;
	}

	if (mhdcCursorBuffer) {
		DeleteDC(mhdcCursorBuffer);
		mhdcCursorBuffer = NULL;
	}

	if (mGL.IsInited()) {
		if (HDC hdc = GetDC(mhwndGL)) {
			if (mGL.Begin(hdc)) {
				if (mGL.NV_occlusion_query) {
					for(int i=0; i<2; ++i) {
						if (mbGLOcclusionValid[i]) {
							GetOcclusionQueryPixelCountSafe(mGLOcclusionQueries[i]);
							mbGLOcclusionValid[i] = false;
						}
					}
					mGL.glDeleteOcclusionQueriesNV(2, mGLOcclusionQueries);
				}
				mGL.End();
			}
			ReleaseDC(mhwndGL, hdc);
		}

		mGL.glDeleteLists(mGLShaderBase, sizeof g_techniques / sizeof g_techniques[0]);

		if (mGLTextures[0]) {
			mGL.glDeleteTextures(2, mGLTextures);
			mGLTextures[0] = 0;
		}

		if (mGLCursorCacheTexture) {
			mGL.glDeleteTextures(1, &mGLCursorCacheTexture);
			mGLCursorCacheTexture = 0;
			mCachedCursor = NULL;
		}

		if (mGL.EXT_pixel_buffer_object)
			mGL.glDeleteBuffersARB(2, mGLBuffers);

		mGL.Shutdown();
	}

	if (mhwndGLDraw) {
		DestroyWindow(mhwndGLDraw);
		mhwndGLDraw = NULL;
	}

	if (mhwndGL) {
		DestroyWindow(mhwndGL);
		mhwndGL = NULL;
	}

	if (mhdcOffscreen) {
		DeleteDC(mhdcOffscreen);
		mhdcOffscreen = NULL;
	}

	if (mhbmOffscreen) {
		DeleteObject(mhbmOffscreen);
		mhbmOffscreen = NULL;
	}
}

void VDCaptureDriverScreen::FlushFrameQueue() {
	if (mbOpenGLMode) {
		for(int i=0; i<2; ++i) {
			if (mbGLOcclusionValid[i]) {
				GetOcclusionQueryPixelCountSafe(mGLOcclusionQueries[i]);
				mbGLOcclusionValid[i] = false;
			}
		}

		for(int i=0; i<sizeof(mTimestampQueue)/sizeof(mTimestampQueue[0]); ++i)
			mTimestampQueue[i] = -1;

		mbFrameValid[0] = mbFrameValid[1] = false;
		mbGLOcclusionValid[0] = mbGLOcclusionValid[1] = false;
		mbGLOcclusionPrevFrameValid = false;
	}
}

sint64 VDCaptureDriverScreen::ComputeGlobalTime() {
	sint64 tickDelta = (sint64)VDGetAccurateTick() - (sint64)mGlobalTimeBase;
	if (tickDelta < 0)
		tickDelta = 0;
	return (sint64)((uint64)tickDelta * 1000);
}

void VDCaptureDriverScreen::DoFrame() {
	if (!mbCapBuffersInited)
		return;

	sint64 globalTime;

	int w = mVideoFormat->biWidth;
	int h = abs(mVideoFormat->biHeight);
	int srcw = w;
	int srch = h;

	if (mbRescaleImage) {
		srcw = mRescaleW;
		srch = mRescaleH;
	}

	if (mbTrackCursor) {
		POINT pt;

		if (GetCursorPos(&pt)) {
			mTrackX = pt.x - ((w+1) >> 1);
			mTrackY = pt.y - ((h+1) >> 1);
		}
	} else {
		mTrackX = mTrackOffsetX;
		mTrackY = mTrackOffsetY;
	}

	if (mbTrackActiveWindow) {
		HWND hwndFore = GetForegroundWindow();

		if (hwndFore) {
			RECT r;
			bool success = false;

			if (mbTrackActiveWindowClient) {
				if (GetClientRect(hwndFore, &r)) {
					if (MapWindowPoints(hwndFore, NULL, (LPPOINT)&r, 2))
						success = true;
				}
			} else {
				if (GetWindowRect(hwndFore, &r))
					success = true;
			}

			if (success) {
				if (!mbTrackCursor) {
					mTrackX = r.left + mTrackOffsetX;
					mTrackY = r.left + mTrackOffsetY;
				}

				if (mTrackX > r.right - srcw)
					mTrackX = r.right - srcw;
				if (mTrackX < r.left)
					mTrackX = r.left;
				if (mTrackY > r.bottom - srch)
					mTrackY = r.bottom - srch;
				if (mTrackY < r.top)
					mTrackY = r.top;
			}
		}
	}

	globalTime = ComputeGlobalTime();
	if (mbCapturing || mDisplayMode) {
		// Check for cursor update.
		CURSORINFO ci = {sizeof(CURSORINFO)};
		bool cursorImageUpdated = false;

		if (mbDrawMousePointer) {
			if (!::GetCursorInfo(&ci)) {
				ci.hCursor = NULL;
			}

			if (ci.hCursor) {
				if (mCachedCursor != ci.hCursor) {
					mCachedCursor = ci.hCursor;

					ICONINFO ii;
					if (::GetIconInfo(ci.hCursor, &ii)) {
						mCachedCursorHotspotX = ii.xHotspot;
						mCachedCursorHotspotY = ii.yHotspot;

						if (mbOpenGLMode) {
							bool mergeMask = false;

							HDC hdc = GetDC(NULL);
							if (hdc) {
								mbCachedCursorXORMode = false;

								if (!ii.hbmColor) {
									mbCachedCursorXORMode = true;

									// Query bitmap format.
									BITMAPINFOHEADER maskFormat = {sizeof(BITMAPINFOHEADER)};
									if (::GetDIBits(hdc, ii.hbmMask, 0, 0, NULL, (LPBITMAPINFO)&maskFormat, DIB_RGB_COLORS)) {
										// Validate cursor size. This shouldn't change since SM_CXCURSOR and SM_CYCURSOR are constant.
										if (maskFormat.biWidth == mCachedCursorWidth && maskFormat.biHeight == mCachedCursorHeight * 2) {
											// Retrieve bitmap bits.
											BITMAPINFOHEADER hdr = {};
											hdr.biSize			= sizeof(BITMAPINFOHEADER);
											hdr.biWidth			= maskFormat.biWidth;
											hdr.biHeight		= maskFormat.biHeight;
											hdr.biPlanes		= 1;
											hdr.biBitCount		= 32;
											hdr.biCompression	= BI_RGB;
											hdr.biSizeImage		= maskFormat.biWidth * maskFormat.biHeight * 4;
											hdr.biXPelsPerMeter	= 0;
											hdr.biYPelsPerMeter	= 0;
											hdr.biClrUsed		= 0;
											hdr.biClrImportant	= 0;

											::GetDIBits(hdc, ii.hbmMask, 0, maskFormat.biHeight, mpCursorBuffer, (LPBITMAPINFO)&hdr, DIB_RGB_COLORS);
										}
									}

									uint32 numPixels = mCachedCursorWidth * mCachedCursorHeight;
									uint32 *pXORMask = mpCursorBuffer;
									uint32 *pANDMask = pXORMask + numPixels;

									for(uint32 i=0; i<numPixels; ++i)
										pXORMask[i] = (pXORMask[i] & 0xFFFFFF) + (~pANDMask[i] << 24);
								} else {
									RECT r1 = {0, 0, mCachedCursorWidth, mCachedCursorHeight};
									RECT r2 = {0, mCachedCursorHeight, mCachedCursorWidth, mCachedCursorHeight*2};
									FillRect(mhdcCursorBuffer, &r1, (HBRUSH)GetStockObject(BLACK_BRUSH));
									FillRect(mhdcCursorBuffer, &r2, (HBRUSH)GetStockObject(WHITE_BRUSH));
									DrawIcon(mhdcCursorBuffer, 0, 0, ci.hCursor);
									DrawIcon(mhdcCursorBuffer, 0, mCachedCursorHeight, ci.hCursor);
									GdiFlush();

									uint32 numPixels = mCachedCursorWidth * mCachedCursorHeight;
									uint32 *pWhiteMask = mpCursorBuffer;
									uint32 *pBlackMask = pWhiteMask + numPixels;

									for(uint32 i=0; i<numPixels; ++i) {
										uint32 pixelOnWhite = pWhiteMask[i];
										uint32 pixelOnBlack = pBlackMask[i];
										int alpha = 255 - (int)(pWhiteMask[i] & 255) + (int)(pBlackMask[i] & 255);
										if ((unsigned)alpha >= 256)
											alpha = ~alpha >> 31;
										pWhiteMask[i] = (pBlackMask[i] & 0xffffff) + (alpha << 24);
									}
								}

								cursorImageUpdated = true;
								ReleaseDC(NULL, hdc);
							}
						}

						if (ii.hbmColor)
							VDVERIFY(::DeleteObject(ii.hbmColor));
						if (ii.hbmMask)
							VDVERIFY(::DeleteObject(ii.hbmMask));
					}
				}

				ci.ptScreenPos.x -= mCachedCursorHotspotX;
				ci.ptScreenPos.y -= mCachedCursorHotspotY;
			}
		}

		if (mbOpenGLMode) {
			RECT r = {0, 0, GetSystemMetrics(SM_CXSCREEN), GetSystemMetrics(SM_CYSCREEN)};

			GetWindowRect(mhwndGL, &r);

			if (HDC hdc = GetDC(mhwndGL)) {
				if (mGL.Begin(hdc)) {
					// init state
					VDASSERT(!mGL.glGetError());
					mGL.glDrawBuffer(GL_BACK);
					mGL.glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);

					// update cursor if necessary
					if (cursorImageUpdated) {
						mGL.glBindTexture(GL_TEXTURE_2D, mGLCursorCacheTexture);
						mGL.glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, mCachedCursorWidth, mCachedCursorHeight, GL_BGRA_EXT, GL_UNSIGNED_BYTE, mpCursorBuffer);
					}

					// read screen into texture
					if (mGL.ARB_multitexture)
						mGL.glActiveTextureARB(GL_TEXTURE0_ARB);
					mGL.glBindTexture(GL_TEXTURE_2D, mGLTextures[0]);

					int srcx = mTrackX - r.left;
					int srcy = r.bottom - (mTrackY + srch);

					if (srcx > r.right - r.left - srcw)
						srcx = r.right - r.left - srcw;
					if (srcy > r.bottom - r.top - srch)
						srcy = r.bottom - r.top - srch;
					if (srcx < 0)
						srcx = 0;
					if (srcy < 0)
						srcy = 0;

					// Enable depth test. We don't actually want the depth test, but we need it
					// on for occlusion query to work on ATI.
					mGL.glEnable(GL_DEPTH_TEST);
					mGL.glDepthFunc(GL_ALWAYS);
					mGL.glDepthMask(GL_FALSE);

					// shrink image
					VDASSERT(!mGL.glGetError());
					mGL.glDisable(GL_LIGHTING);
					mGL.glDisable(GL_CULL_FACE);
					mGL.glDisable(GL_BLEND);
					mGL.glDisable(GL_ALPHA_TEST);
					mGL.glDisable(GL_STENCIL_TEST);
					mGL.glDisable(GL_SCISSOR_TEST);
					mGL.glEnable(GL_TEXTURE_2D);

					mGL.DisableFragmentShaders();

					int fbw = srcw < w ? w : srcw;
					int fbh = srch < h ? h : srch;

					mGL.glViewport(0, 0, fbw, fbh);
					mGL.glMatrixMode(GL_MODELVIEW);
					mGL.glLoadIdentity();
					mGL.glMatrixMode(GL_PROJECTION);
					mGL.glLoadIdentity();
					mGL.glOrtho(0, fbw, 0, fbh, -1.0f, 1.0f);

					VDASSERT(!mGL.glGetError());
					mGL.glReadBuffer(GL_FRONT);

					mProfileChannel.Begin(0xd0e0f0, "GL:ReadScreen");
					if (!mbRescaleImage || mbDrawMousePointer) {
						mGL.glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, srcx, srcy, srcw, srch);
						mGL.glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);

						float u = (float)srcw / (float)mGLTextureW;
						float v = (float)srch / (float)mGLTextureH;

						mGL.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
						mGL.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
						mGL.glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
						mGL.glBegin(GL_QUADS);
							mGL.glTexCoord2f(0.0f, 0.0f);
							mGL.glVertex2f(0, 0);
							mGL.glTexCoord2f(u, 0.0f);
							mGL.glVertex2f((float)srcw, 0);
							mGL.glTexCoord2f(u, v);
							mGL.glVertex2f((float)srcw, (float)srch);
							mGL.glTexCoord2f(0.0f, v);
							mGL.glVertex2f(0, (float)srch);
						mGL.glEnd();
						mGL.glReadBuffer(GL_BACK);

						if (ci.hCursor) {
							mGL.glBindTexture(GL_TEXTURE_2D, mGLCursorCacheTexture);
							mGL.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
							mGL.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
							mGL.glColor4f(1.0f, 1.0f, 1.0f, 1.0f);

							int curx = ci.ptScreenPos.x - srcx;
							int cury = (r.bottom - (ci.ptScreenPos.y + mCachedCursorHeight)) - srcy;
							float curu = (float)mCachedCursorWidth * mGLCursorCacheTextureInvW;
							float curv = (float)mCachedCursorHeight * mGLCursorCacheTextureInvH;
							mGL.glEnable(GL_BLEND);

							if (mbCachedCursorXORMode && mGL.EXT_texture_env_combine) {
								mGL.glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_EXT);
								mGL.glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_EXT, GL_MODULATE);
								mGL.glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_EXT, GL_TEXTURE);
								mGL.glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB_EXT, GL_ONE_MINUS_SRC_ALPHA);
								mGL.glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB_EXT, GL_TEXTURE);
								mGL.glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB_EXT, GL_SRC_COLOR);
								mGL.glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA_EXT, GL_REPLACE);
								mGL.glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_EXT, GL_TEXTURE);
								mGL.glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA_EXT, GL_SRC_ALPHA);
								mGL.glTexEnvi(GL_TEXTURE_ENV, GL_RGB_SCALE_EXT, 1);
								mGL.glTexEnvi(GL_TEXTURE_ENV, GL_ALPHA_SCALE, 1);
								mGL.glBlendFunc(GL_ONE_MINUS_DST_COLOR, GL_ONE_MINUS_SRC_COLOR);
								mGL.glBegin(GL_QUADS);
									mGL.glTexCoord2f(0.0f, 0.0f);
									mGL.glVertex2i(curx, cury);
									mGL.glTexCoord2f(curu, 0.0f);
									mGL.glVertex2i(curx + mCachedCursorWidth, cury);
									mGL.glTexCoord2f(curu, curv);
									mGL.glVertex2i(curx + mCachedCursorWidth, cury + mCachedCursorHeight);
									mGL.glTexCoord2f(0.0f, curv);
									mGL.glVertex2i(curx, cury + mCachedCursorHeight);
								mGL.glEnd();
								mGL.glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
								mGL.glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
							} else
								mGL.glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);

							mGL.glBegin(GL_QUADS);
								mGL.glTexCoord2f(0.0f, 0.0f);
								mGL.glVertex2i(curx, cury);
								mGL.glTexCoord2f(curu, 0.0f);
								mGL.glVertex2i(curx + mCachedCursorWidth, cury);
								mGL.glTexCoord2f(curu, curv);
								mGL.glVertex2i(curx + mCachedCursorWidth, cury + mCachedCursorHeight);
								mGL.glTexCoord2f(0.0f, curv);
								mGL.glVertex2i(curx, cury + mCachedCursorHeight);
							mGL.glEnd();
							mGL.glDisable(GL_BLEND);
							mGL.glBindTexture(GL_TEXTURE_2D, mGLTextures[0]);
						}
					}

					if (mbRescaleImage) {
						do {
							int dstw = std::max<int>(w, (srcw+1) >> 1);
							int dsth = std::max<int>(h, (srch+1) >> 1);

							mGL.glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, srcx, srcy, srcw, srch);
							srcx = 0;
							srcy = 0;

							mGL.glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);

							float u = (float)srcw / (float)mGLTextureW;
							float v = (float)srch / (float)mGLTextureH;

							mGL.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
							mGL.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
							mGL.glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
							mGL.glBegin(GL_QUADS);
								mGL.glTexCoord2f(0.0f, 0.0f);
								mGL.glVertex2f(0, 0);
								mGL.glTexCoord2f(u, 0.0f);
								mGL.glVertex2f((float)dstw, 0);
								mGL.glTexCoord2f(u, v);
								mGL.glVertex2f((float)dstw, (float)dsth);
								mGL.glTexCoord2f(0.0f, v);
								mGL.glVertex2f(0, (float)dsth);
							mGL.glEnd();

							mGL.glReadBuffer(GL_BACK);
							srcw = dstw;
							srch = dsth;
						} while(srcw != w || srch != h);
					}
					mProfileChannel.End();

					VDASSERT(!mGL.glGetError());

					// compute texturing parameters
					float u = (float)w / (float)mGLTextureW;
					float v = (float)h / (float)mGLTextureH;

					bool removeDuplicates = mGL.NV_occlusion_query && mGL.ARB_multitexture && mbRemoveDuplicates;
					if (mVideoFormat->biCompression || removeDuplicates || mDisplayMode == kDisplaySoftware || mDisplayMode == kDisplayHardware) {
						mGL.glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, w, h);

						if (removeDuplicates) {
							if (!mbGLOcclusionPrevFrameValid) {
								mbGLOcclusionPrevFrameValid = true;
								mbFrameValid[0] = true;
							} else {
								mProfileChannel.Begin(0xa0c0f0, "GL:OcclusionQuery");
								mGL.glActiveTextureARB(GL_TEXTURE1_ARB);
								mGL.glEnable(GL_TEXTURE_2D);
								mGL.glBindTexture(GL_TEXTURE_2D, mGLTextures[1]);

								mGL.glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
								mGL.glEnable(GL_ALPHA_TEST);
								mGL.glAlphaFunc(GL_GREATER, 0.0f);
								
								mGL.glBeginOcclusionQueryNV(mGLOcclusionQueries[0]);
								if (mGL.NV_register_combiners)
									mGL.glCallList(mGLShaderBase + kVDOpenGLTechIndex_difference_NV1x);
								else if (mGL.ATI_fragment_shader)
									mGL.glCallList(mGLShaderBase + kVDOpenGLTechIndex_difference_ATIFS);

								mGL.glBegin(GL_QUADS);
									mGL.glMultiTexCoord2fARB(GL_TEXTURE0_ARB, 0.0f, 0.0f);
									mGL.glMultiTexCoord2fARB(GL_TEXTURE1_ARB, 0.0f, 0.0f);
									mGL.glVertex2f(0, 0);
									mGL.glMultiTexCoord2fARB(GL_TEXTURE0_ARB, u, 0.0f);
									mGL.glMultiTexCoord2fARB(GL_TEXTURE1_ARB, u, 0.0f);
									mGL.glVertex2f((float)w, 0);
									mGL.glMultiTexCoord2fARB(GL_TEXTURE0_ARB, u, v);
									mGL.glMultiTexCoord2fARB(GL_TEXTURE1_ARB, u, v);
									mGL.glVertex2f((float)w, (float)h);
									mGL.glMultiTexCoord2fARB(GL_TEXTURE0_ARB, 0.0f, v);
									mGL.glMultiTexCoord2fARB(GL_TEXTURE1_ARB, 0.0f, v);
									mGL.glVertex2f(0, (float)h);
								mGL.glEnd();
								mGL.glEndOcclusionQueryNV();
								mGL.glFlush();

								mbGLOcclusionValid[0] = true;

								mGL.glDisable(GL_ALPHA_TEST);
								mGL.glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);

								std::swap(mGLTextures[0], mGLTextures[1]);
								std::swap(mGLOcclusionQueries[0], mGLOcclusionQueries[1]);
								std::swap(mbGLOcclusionValid[0], mbGLOcclusionValid[1]);
								mGL.glActiveTextureARB(GL_TEXTURE1_ARB);
								mGL.glDisable(GL_TEXTURE_2D);
								mGL.glActiveTextureARB(GL_TEXTURE0_ARB);
								mGL.glBindTexture(GL_TEXTURE_2D, mGLTextures[0]);

								GLint pixelCount = 0;

								if (mbGLOcclusionValid[0]) {
									pixelCount = GetOcclusionQueryPixelCountSafe(mGLOcclusionQueries[0]);
									mbGLOcclusionValid[0] = false;
								}

								mbFrameValid[0] = (pixelCount > 0);

								VDASSERT(!mGL.glGetError());
								std::swap(mbFrameValid[0], mbFrameValid[1]);
								mProfileChannel.End();
							}
						} else
							mbFrameValid[0] = mbFrameValid[1] = true;
					} else
						mbFrameValid[0] = mbFrameValid[1] = true;

					mProfileChannel.Begin(0xa0ffa0, "GL:ConvertAndRead");
					switch(mVideoFormat->biCompression) {
					case VDMAKEFOURCC('Y', 'V', '1', '2'):
						if (mGL.ATI_fragment_shader)
							ConvertToYV12_GL_ATIFS(w, h, u, v);
						else if (mGL.NV_register_combiners) {
							if (mGL.NV_register_combiners2)
								ConvertToYV12_GL_NV2x(w, h, u, v);
							else
								ConvertToYV12_GL_NV1x(w, h, u, v);
						}
						break;
					case VDMAKEFOURCC('Y', 'U', 'Y', '2'):
						if (mGL.ATI_fragment_shader)
							ConvertToYUY2_GL_NV2x_ATIFS(w, h, u, v, true);
						else if (mGL.NV_register_combiners) {
							if (mGL.NV_register_combiners2)
								ConvertToYUY2_GL_NV2x_ATIFS(w, h, u, v, false);
							else
								ConvertToYUY2_GL_NV1x(w, h, u, v);
						}
						break;
					default:
						mGL.glReadBuffer(GL_BACK);
						if (mGL.EXT_pixel_buffer_object) {
							mGL.glBindBufferARB(GL_PIXEL_PACK_BUFFER_ARB, mGLBuffers[0]);
							mGL.glReadPixels(0, 0, w, h, GL_BGRA_EXT, GL_UNSIGNED_BYTE, (void *)0);
						} else
							mGL.glReadPixels(0, 0, w, h, GL_BGRA_EXT, GL_UNSIGNED_BYTE, (void *)mGLReadBuffer.data());

						break;
					}
					mProfileChannel.End();

					// readback!

					mTimestampQueue[mTimestampIndex & 3] = globalTime;
					globalTime = mTimestampQueue[(mTimestampIndex + mTimestampDelay) & 3];
					++mTimestampIndex;

					if (globalTime >= 0) {
						if (mGL.EXT_pixel_buffer_object) {
							if (mbFrameValid[0]) {
								mGL.glBindBufferARB(GL_PIXEL_PACK_BUFFER_ARB, mGLBuffers[1]);
								mProfileChannel.Begin(0xa0a0ff, "GL:MapBuffer");
								void *p = mGL.glMapBufferARB(GL_PIXEL_PACK_BUFFER_ARB, GL_READ_ONLY_ARB);
								mProfileChannel.End();
								DispatchFrame(p, mOffscreenSize, globalTime);
								mGL.glUnmapBufferARB(GL_PIXEL_PACK_BUFFER_ARB);
							} else
								DispatchFrame(NULL, 0, globalTime);

							std::swap(mGLBuffers[0], mGLBuffers[1]);
						} else {
							if (mbFrameValid[0])
								DispatchFrame(mGLReadBuffer.data(), mOffscreenSize, globalTime);
							else
								DispatchFrame(NULL, 0, globalTime);
						}
					}

					VDASSERT(!mGL.glGetError());

					// necessary for ATI to work
					if (mGL.ATI_fragment_shader) {
						if (mGL.EXT_swap_control)
							mGL.wglSwapIntervalEXT(0);
						mGL.wglSwapBuffers(hdc);
					}

					mGL.End();
				}
				ReleaseDC(mhwndGL, hdc);
			}
		} else {
			mProfileChannel.Begin(0xf0d0d0, "Capture (GDI)");
			if (HDC hdc = GetDC(NULL)) {
				static DWORD sBitBltMode = AutodetectCaptureBltMode();
				BitBlt(mhdcOffscreen, 0, 0, w, h, hdc, mTrackX, mTrackY, sBitBltMode);
				if (ci.hCursor)
					DrawIcon(mhdcOffscreen, ci.ptScreenPos.x - mTrackX, ci.ptScreenPos.y - mTrackY, ci.hCursor);
				ReleaseDC(NULL, hdc);
			}
			mProfileChannel.End();

			if (mDisplayMode == kDisplaySoftware || mDisplayMode == kDisplayAnalyze) {
				mProfileChannel.Begin(0xe0e0e0, "Preview (GDI)");
				if (HDC hdc = GetDC(mhwnd)) {
					BitBlt(hdc, 0, 0, w, h, mhdcOffscreen, 0, 0, SRCCOPY);
					ReleaseDC(mhwnd, hdc);
				}
				mProfileChannel.End();
			}

			GdiFlush();
			DispatchFrame(mpOffscreenData, mOffscreenSize, globalTime);
		}
	}
	
	if (mbVisible) {
		if (mbOpenGLMode) {
			if (mDisplayMode == kDisplayHardware || mDisplayMode == kDisplaySoftware) {
				RECT rdraw;
				GetClientRect(mhwndGLDraw, &rdraw);

				if (rdraw.right && rdraw.bottom) {
					mProfileChannel.Begin(0xe0e0e0, "Overlay (OpenGL)");
					if (HDC hdcDraw = GetDC(mhwndGLDraw)) {
						if (mGL.Begin(hdcDraw)) {
							VDASSERT(!mGL.glGetError());
							mGL.glDisable(GL_LIGHTING);
							mGL.glDisable(GL_CULL_FACE);
							mGL.glDisable(GL_BLEND);
							mGL.glDisable(GL_ALPHA_TEST);
							mGL.glDisable(GL_DEPTH_TEST);
							mGL.glDisable(GL_STENCIL_TEST);
							mGL.glDisable(GL_SCISSOR_TEST);
							mGL.glEnable(GL_TEXTURE_2D);

							VDASSERT(!mGL.glGetError());
							mGL.DisableFragmentShaders();

							float dstw = (float)rdraw.right;
							float dsth = (float)rdraw.bottom;

							VDASSERT(!mGL.glGetError());
							mGL.glViewport(0, 0, rdraw.right, rdraw.bottom);
							mGL.glMatrixMode(GL_MODELVIEW);
							mGL.glLoadIdentity();
							mGL.glMatrixMode(GL_PROJECTION);
							mGL.glLoadIdentity();
							mGL.glOrtho(0, dstw, 0, dsth, -1.0f, 1.0f);

							mGL.glDrawBuffer(GL_BACK);
							mGL.glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
							mGL.glClearColor(0.5f, 0.0f, 0.0f, 0.0f);
							mGL.glClear(GL_COLOR_BUFFER_BIT);
							VDASSERT(!mGL.glGetError());

							mGL.glBindTexture(GL_TEXTURE_2D, mGLTextures[0]);
							VDASSERT(!mGL.glGetError());

							mGL.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
							mGL.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
							mGL.glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
							mGL.glColor4f(1.0f, 1.0f, 1.0f, 1.0f);

							float u = (float)srcw / (float)mGLTextureW;
							float v = (float)srch / (float)mGLTextureH;

							VDASSERT(!mGL.glGetError());
							mGL.glBegin(GL_QUADS);
								mGL.glTexCoord2f(0.0f, 0.0f);
								mGL.glVertex2f(0, 0);
								mGL.glTexCoord2f(u, 0.0f);
								mGL.glVertex2f(dstw, 0);
								mGL.glTexCoord2f(u, v);
								mGL.glVertex2f(dstw, dsth);
								mGL.glTexCoord2f(0.0f, v);
								mGL.glVertex2f(0, dsth);
							mGL.glEnd();
							VDASSERT(!mGL.glGetError());

							if (mGL.EXT_swap_control)
								mGL.wglSwapIntervalEXT(0);

							mGL.wglSwapBuffers(hdcDraw);
							mGL.End();
						}
						ReleaseDC(mhwndGLDraw, hdcDraw);
					}
					mProfileChannel.End();
				}
			}
		} else {
			if (mDisplayMode == kDisplayHardware) {
				mProfileChannel.Begin(0xe0e0e0, "Overlay (GDI)");
				if (HDC hdcScreen = GetDC(NULL)) {
					if (HDC hdc = GetDC(mhwnd)) {
						BitBlt(hdc, 0, 0, w, h, hdcScreen, mTrackX, mTrackY, SRCCOPY);
						ReleaseDC(mhwnd, hdc);
					}
					ReleaseDC(NULL, hdcScreen);
				}
				mProfileChannel.End();
			}
		}
	}

	if (mDisplayMode)
		++mPreviewFrameCount;
}

void VDCaptureDriverScreen::DispatchFrame(const void *data, uint32 size, sint64 timestamp) {
	if (!mpCB)
		return;

	if (mDisplayMode == kDisplayAnalyze && size) {
		try {
			mpCB->CapProcessData(-1, data, size, 0, true, 0);
		} catch(MyError&) {
			// Eat preview errors.
		}
	}

	if (mbCapturing) {
		try {
			if (mpCB->CapEvent(kEventCapturing, 0))
				mpCB->CapProcessData(0, data, size, timestamp, true, timestamp);
			else {
				mbCapturing = false;
				CaptureAbort();
			}

		} catch(MyError& e) {
			mCaptureError.TransferFrom(e);
			CaptureAbort();
			mbCapturing = false;
		}
	}
}

void VDCaptureDriverScreen::ConvertToYV12_GL_NV1x(int w, int h, float u, float v) {
	// YV12 conversion - NV_register_combiners path
	mGL.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	mGL.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	mGL.glActiveTextureARB(GL_TEXTURE1_ARB);
	mGL.glEnable(GL_TEXTURE_2D);
	mGL.glBindTexture(GL_TEXTURE_2D, mGLTextures[0]);
	mGL.glActiveTextureARB(GL_TEXTURE0_ARB);

	float y = 0.0f;
	float w4 = (float)w * 0.25f;
	for(int phase=0; phase<4; phase += 2) {
		float u0 = ((float)phase - 1.5f) / (float)mGLTextureW;
		float v0 = 0;
		float u1 = u0 + u;
		float v1 = v0 + v;

		float u2 = ((float)phase - 0.5f) / (float)mGLTextureW;
		float u3 = u2 + u;

		mGL.glCallList(mGLShaderBase + (phase ? kVDOpenGLTechIndex_YV12_NV1x_Y_ra : kVDOpenGLTechIndex_YV12_NV1x_Y_gb));
		mGL.glColorMask(GL_TRUE, phase==0, phase==0, GL_TRUE);

		float y0 = y;
		float y1 = y + (float)h;

		mGL.glColor4f(0.0f, 0.0f, 1.0f, 0.0f);

		mGL.glBegin(GL_QUADS);
			mGL.glMultiTexCoord2fARB(GL_TEXTURE0_ARB, u0, v1);	mGL.glMultiTexCoord2fARB(GL_TEXTURE1_ARB, u2, v1);	mGL.glVertex2f(0.0f, y0);
			mGL.glMultiTexCoord2fARB(GL_TEXTURE0_ARB, u1, v1);	mGL.glMultiTexCoord2fARB(GL_TEXTURE1_ARB, u3, v1);	mGL.glVertex2f(w4, y0);
			mGL.glMultiTexCoord2fARB(GL_TEXTURE0_ARB, u1, v0);	mGL.glMultiTexCoord2fARB(GL_TEXTURE1_ARB, u3, v0);	mGL.glVertex2f(w4, y1);
			mGL.glMultiTexCoord2fARB(GL_TEXTURE0_ARB, u0, v0);	mGL.glMultiTexCoord2fARB(GL_TEXTURE1_ARB, u2, v0);	mGL.glVertex2f(0.0f, y1);
		mGL.glEnd();
		VDASSERT(!mGL.glGetError());
	}

	y = 0.0f;

	float w8 = (float)w * 0.125f;
	int h2 = h >> 1;
	for(int cmode=0; cmode<2; ++cmode) {
		mGL.glCallList(mGLShaderBase + (cmode ? kVDOpenGLTechIndex_YV12_NV1x_Cb : kVDOpenGLTechIndex_YV12_NV1x_Cr));
		for(int phase=0; phase<4; phase += 2) {
			float u0 = (float)(phase * 2 - 3.0f) / (float)mGLTextureW;
			float v0 = 0;
			float u1 = u0 + u;
			float v1 = v0 + v;

			float u2 = (float)(phase * 2 - 1.0f) / (float)mGLTextureW;
			float u3 = u0 + u;

			mGL.glColorMask(GL_TRUE, phase==0, phase==0, GL_TRUE);

			float y0 = y;
			float y1 = y + (float)h2;

			mGL.glBegin(GL_QUADS);
				mGL.glMultiTexCoord2fARB(GL_TEXTURE0_ARB, u0, v1);	mGL.glMultiTexCoord2fARB(GL_TEXTURE1_ARB, u2, v1);	mGL.glVertex2f(w4, y0);
				mGL.glMultiTexCoord2fARB(GL_TEXTURE0_ARB, u1, v1);	mGL.glMultiTexCoord2fARB(GL_TEXTURE1_ARB, u3, v1);	mGL.glVertex2f(w4+w8, y0);
				mGL.glMultiTexCoord2fARB(GL_TEXTURE0_ARB, u1, v0);	mGL.glMultiTexCoord2fARB(GL_TEXTURE1_ARB, u3, v0);	mGL.glVertex2f(w4+w8, y1);
				mGL.glMultiTexCoord2fARB(GL_TEXTURE0_ARB, u0, v0);	mGL.glMultiTexCoord2fARB(GL_TEXTURE1_ARB, u2, v0);	mGL.glVertex2f(w4, y1);
			mGL.glEnd();
			VDASSERT(!mGL.glGetError());
		}

		y += h2;
	}

	mGL.glActiveTextureARB(GL_TEXTURE1_ARB);
	mGL.glDisable(GL_TEXTURE_2D);
	mGL.glActiveTextureARB(GL_TEXTURE0_ARB);

	// readback!
	VDASSERT(!mGL.glGetError());
	mGL.glReadBuffer(GL_BACK);

	if (mGL.EXT_pixel_buffer_object) {
		mGL.glBindBufferARB(GL_PIXEL_PACK_BUFFER_ARB, mGLBuffers[0]);
		mGL.glReadPixels(0, 0, w >> 2, h, GL_BGRA_EXT, GL_UNSIGNED_BYTE, (void *)0);
		mGL.glReadPixels(w >> 2, 0, w >> 3, h >> 1, GL_BGRA_EXT, GL_UNSIGNED_BYTE, (void *)(w * h));
		mGL.glReadPixels(w >> 2, h >> 1, w >> 3, h >> 1, GL_BGRA_EXT, GL_UNSIGNED_BYTE, (void *)(w * h * 5 / 4));
	} else {
		char *dst = (char *)mGLReadBuffer.data();
		mGL.glReadPixels(0, 0, w >> 2, h, GL_BGRA_EXT, GL_UNSIGNED_BYTE, dst);
		mGL.glReadPixels(w >> 2, 0, w >> 3, h >> 1, GL_BGRA_EXT, GL_UNSIGNED_BYTE, dst + (w * h));
		mGL.glReadPixels(w >> 2, h >> 1, w >> 3, h >> 1, GL_BGRA_EXT, GL_UNSIGNED_BYTE, dst + (w * h * 5 / 4));
	}
}

void VDCaptureDriverScreen::ConvertToYV12_GL_NV2x(int w, int h, float u, float v) {
	// YV12 conversion - NV_register_combiners2 path
	mGL.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	mGL.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	mGL.glActiveTextureARB(GL_TEXTURE3_ARB);
	mGL.glEnable(GL_TEXTURE_2D);
	mGL.glBindTexture(GL_TEXTURE_2D, mGLTextures[0]);
	mGL.glActiveTextureARB(GL_TEXTURE2_ARB);
	mGL.glEnable(GL_TEXTURE_2D);
	mGL.glBindTexture(GL_TEXTURE_2D, mGLTextures[0]);
	mGL.glActiveTextureARB(GL_TEXTURE1_ARB);
	mGL.glEnable(GL_TEXTURE_2D);
	mGL.glBindTexture(GL_TEXTURE_2D, mGLTextures[0]);
	mGL.glActiveTextureARB(GL_TEXTURE0_ARB);

	float y = 0.0f;
	float w4 = (float)w * 0.25f;
	{
		float u0 = -1.5f / (float)mGLTextureW;
		float v0 = 0;
		float u1 = u0 + u;
		float v1 = v0 + v;

		float u2 = -0.5f / (float)mGLTextureW;
		float u3 = u2 + u;

		float u4 = +0.5f / (float)mGLTextureW;
		float u5 = u4 + u;

		float u6 = +1.5f / (float)mGLTextureW;
		float u7 = u6 + u;

		mGL.glCallList(mGLShaderBase + kVDOpenGLTechIndex_YV12_NV2x_Y);
		mGL.glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);

		float y0 = y;
		float y1 = y + (float)h;

		mGL.glColor4f(0.0f, 0.0f, 1.0f, 0.0f);

		mGL.glBegin(GL_QUADS);
			mGL.glMultiTexCoord2fARB(GL_TEXTURE0_ARB, u0, v1);
			mGL.glMultiTexCoord2fARB(GL_TEXTURE1_ARB, u2, v1);
			mGL.glMultiTexCoord2fARB(GL_TEXTURE2_ARB, u4, v1);
			mGL.glMultiTexCoord2fARB(GL_TEXTURE3_ARB, u6, v1);
			mGL.glVertex2f(0.0f, y0);

			mGL.glMultiTexCoord2fARB(GL_TEXTURE0_ARB, u1, v1);
			mGL.glMultiTexCoord2fARB(GL_TEXTURE1_ARB, u3, v1);
			mGL.glMultiTexCoord2fARB(GL_TEXTURE2_ARB, u5, v1);
			mGL.glMultiTexCoord2fARB(GL_TEXTURE3_ARB, u7, v1);
			mGL.glVertex2f(w4, y0);

			mGL.glMultiTexCoord2fARB(GL_TEXTURE0_ARB, u1, v0);
			mGL.glMultiTexCoord2fARB(GL_TEXTURE1_ARB, u3, v0);
			mGL.glMultiTexCoord2fARB(GL_TEXTURE2_ARB, u5, v0);
			mGL.glMultiTexCoord2fARB(GL_TEXTURE3_ARB, u7, v0);
			mGL.glVertex2f(w4, y1);

			mGL.glMultiTexCoord2fARB(GL_TEXTURE0_ARB, u0, v0);
			mGL.glMultiTexCoord2fARB(GL_TEXTURE1_ARB, u2, v0);
			mGL.glMultiTexCoord2fARB(GL_TEXTURE2_ARB, u4, v0);
			mGL.glMultiTexCoord2fARB(GL_TEXTURE3_ARB, u6, v0);
			mGL.glVertex2f(0.0f, y1);
		mGL.glEnd();
		VDASSERT(!mGL.glGetError());
	}

	mGL.glActiveTextureARB(GL_TEXTURE3_ARB);
	mGL.glDisable(GL_TEXTURE_2D);

	y = 0.0f;

	float w8 = (float)w * 0.125f;
	int h2 = h >> 1;
	for(int cmode=0; cmode<2; ++cmode) {
		mGL.glCallList(mGLShaderBase + (cmode ? kVDOpenGLTechIndex_YV12_NV2x_Cb : kVDOpenGLTechIndex_YV12_NV2x_Cr));
		for(int phase=0; phase<4; phase += 2) {
			float u0 = (float)(phase * 2 - 4.0f) / (float)mGLTextureW;
			float v0 = 0;
			float u1 = u0 + u;
			float v1 = v0 + v;

			float u2 = (float)(phase * 2 - 2.0f) / (float)mGLTextureW;
			float u3 = u2 + u;

			float u4 = (float)(phase * 2 - 0.0f) / (float)mGLTextureW;
			float u5 = u4 + u;

			mGL.glColorMask(GL_TRUE, phase==0, phase==0, GL_TRUE);

			float y0 = y;
			float y1 = y + (float)h2;

			mGL.glBegin(GL_QUADS);
				mGL.glMultiTexCoord2fARB(GL_TEXTURE0_ARB, u0, v1);
				mGL.glMultiTexCoord2fARB(GL_TEXTURE1_ARB, u2, v1);
				mGL.glMultiTexCoord2fARB(GL_TEXTURE2_ARB, u4, v1);
				mGL.glVertex2f(w4, y0);
				mGL.glMultiTexCoord2fARB(GL_TEXTURE0_ARB, u1, v1);
				mGL.glMultiTexCoord2fARB(GL_TEXTURE1_ARB, u3, v1);
				mGL.glMultiTexCoord2fARB(GL_TEXTURE2_ARB, u5, v1);
				mGL.glVertex2f(w4+w8, y0);
				mGL.glMultiTexCoord2fARB(GL_TEXTURE0_ARB, u1, v0);
				mGL.glMultiTexCoord2fARB(GL_TEXTURE1_ARB, u3, v0);
				mGL.glMultiTexCoord2fARB(GL_TEXTURE2_ARB, u5, v0);
				mGL.glVertex2f(w4+w8, y1);
				mGL.glMultiTexCoord2fARB(GL_TEXTURE0_ARB, u0, v0);
				mGL.glMultiTexCoord2fARB(GL_TEXTURE1_ARB, u2, v0);
				mGL.glMultiTexCoord2fARB(GL_TEXTURE2_ARB, u4, v0);
				mGL.glVertex2f(w4, y1);
			mGL.glEnd();
			VDASSERT(!mGL.glGetError());
		}

		y += h2;
	}

	mGL.glActiveTextureARB(GL_TEXTURE2_ARB);
	mGL.glDisable(GL_TEXTURE_2D);
	mGL.glActiveTextureARB(GL_TEXTURE1_ARB);
	mGL.glDisable(GL_TEXTURE_2D);
	mGL.glActiveTextureARB(GL_TEXTURE0_ARB);

	// readback!
	VDASSERT(!mGL.glGetError());
	mGL.glReadBuffer(GL_BACK);

	if (mGL.EXT_pixel_buffer_object) {
		mGL.glBindBufferARB(GL_PIXEL_PACK_BUFFER_ARB, mGLBuffers[0]);
		mGL.glReadPixels(0, 0, w >> 2, h, GL_BGRA_EXT, GL_UNSIGNED_BYTE, (void *)0);
		mGL.glReadPixels(w >> 2, 0, w >> 3, h >> 1, GL_BGRA_EXT, GL_UNSIGNED_BYTE, (void *)(w * h));
		mGL.glReadPixels(w >> 2, h >> 1, w >> 3, h >> 1, GL_BGRA_EXT, GL_UNSIGNED_BYTE, (void *)(w * h * 5 / 4));
	} else {
		char *dst = (char *)mGLReadBuffer.data();
		mGL.glReadPixels(0, 0, w >> 2, h, GL_BGRA_EXT, GL_UNSIGNED_BYTE, dst);
		mGL.glReadPixels(w >> 2, 0, w >> 3, h >> 1, GL_BGRA_EXT, GL_UNSIGNED_BYTE, dst + (w * h));
		mGL.glReadPixels(w >> 2, h >> 1, w >> 3, h >> 1, GL_BGRA_EXT, GL_UNSIGNED_BYTE, dst + (w * h * 5 / 4));
	}
}

void VDCaptureDriverScreen::ConvertToYV12_GL_ATIFS(int w, int h, float u, float v) {
	// YV12 conversion - NV_register_combiners2 path
	mGL.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	mGL.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	mGL.glActiveTextureARB(GL_TEXTURE3_ARB);
	mGL.glEnable(GL_TEXTURE_2D);
	mGL.glBindTexture(GL_TEXTURE_2D, mGLTextures[0]);
	mGL.glActiveTextureARB(GL_TEXTURE2_ARB);
	mGL.glEnable(GL_TEXTURE_2D);
	mGL.glBindTexture(GL_TEXTURE_2D, mGLTextures[0]);
	mGL.glActiveTextureARB(GL_TEXTURE1_ARB);
	mGL.glEnable(GL_TEXTURE_2D);
	mGL.glBindTexture(GL_TEXTURE_2D, mGLTextures[0]);
	mGL.glActiveTextureARB(GL_TEXTURE0_ARB);

	float y = 0.0f;
	float w4 = (float)w * 0.25f;
	{
		float u0 = -1.5f / (float)mGLTextureW;
		float v0 = 0;
		float u1 = u0 + u;
		float v1 = v0 + v;

		float u2 = -0.5f / (float)mGLTextureW;
		float u3 = u2 + u;

		float u4 = +0.5f / (float)mGLTextureW;
		float u5 = u4 + u;

		float u6 = +1.5f / (float)mGLTextureW;
		float u7 = u6 + u;

		mGL.glCallList(mGLShaderBase + kVDOpenGLTechIndex_YV12_ATIFS_Y);
		mGL.glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);

		float y0 = y;
		float y1 = y + (float)h;

		mGL.glColor4f(0.0f, 0.0f, 1.0f, 0.0f);

		mGL.glBegin(GL_QUADS);
			mGL.glMultiTexCoord2fARB(GL_TEXTURE0_ARB, u0, v1);
			mGL.glMultiTexCoord2fARB(GL_TEXTURE1_ARB, u2, v1);
			mGL.glMultiTexCoord2fARB(GL_TEXTURE2_ARB, u4, v1);
			mGL.glMultiTexCoord2fARB(GL_TEXTURE3_ARB, u6, v1);
			mGL.glVertex2f(0.0f, y0);

			mGL.glMultiTexCoord2fARB(GL_TEXTURE0_ARB, u1, v1);
			mGL.glMultiTexCoord2fARB(GL_TEXTURE1_ARB, u3, v1);
			mGL.glMultiTexCoord2fARB(GL_TEXTURE2_ARB, u5, v1);
			mGL.glMultiTexCoord2fARB(GL_TEXTURE3_ARB, u7, v1);
			mGL.glVertex2f(w4, y0);

			mGL.glMultiTexCoord2fARB(GL_TEXTURE0_ARB, u1, v0);
			mGL.glMultiTexCoord2fARB(GL_TEXTURE1_ARB, u3, v0);
			mGL.glMultiTexCoord2fARB(GL_TEXTURE2_ARB, u5, v0);
			mGL.glMultiTexCoord2fARB(GL_TEXTURE3_ARB, u7, v0);
			mGL.glVertex2f(w4, y1);

			mGL.glMultiTexCoord2fARB(GL_TEXTURE0_ARB, u0, v0);
			mGL.glMultiTexCoord2fARB(GL_TEXTURE1_ARB, u2, v0);
			mGL.glMultiTexCoord2fARB(GL_TEXTURE2_ARB, u4, v0);
			mGL.glMultiTexCoord2fARB(GL_TEXTURE3_ARB, u6, v0);
			mGL.glVertex2f(0.0f, y1);
		mGL.glEnd();
		VDASSERT(!mGL.glGetError());
	}

	mGL.glActiveTextureARB(GL_TEXTURE4_ARB);
	mGL.glEnable(GL_TEXTURE_2D);
	mGL.glBindTexture(GL_TEXTURE_2D, mGLTextures[0]);

	y = 0.0f;

	float w8 = (float)w * 0.125f;
	int h2 = h >> 1;
	mGL.glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
	for(int cmode=0; cmode<2; ++cmode) {
		mGL.glCallList(mGLShaderBase + (cmode ? kVDOpenGLTechIndex_YV12_ATIFS_Cb : kVDOpenGLTechIndex_YV12_ATIFS_Cr));
		float u0 = -4.5f / (float)mGLTextureW;		float u1 = u0 + u;
		float u2 = -2.5f / (float)mGLTextureW;		float u3 = u2 + u;
		float u4 = -0.5f / (float)mGLTextureW;		float u5 = u4 + u;
		float u6 = +0.5f / (float)mGLTextureW;		float u7 = u6 + u;
		float u8 = +2.5f / (float)mGLTextureW;		float u9 = u8 + u;

		float v0 = 0;
		float v1 = v0 + v;

		float y0 = y;
		float y1 = y + (float)h2;

		mGL.glBegin(GL_QUADS);
			mGL.glMultiTexCoord2fARB(GL_TEXTURE0_ARB, u0, v1);
			mGL.glMultiTexCoord2fARB(GL_TEXTURE1_ARB, u2, v1);
			mGL.glMultiTexCoord2fARB(GL_TEXTURE2_ARB, u4, v1);
			mGL.glMultiTexCoord2fARB(GL_TEXTURE3_ARB, u6, v1);
			mGL.glMultiTexCoord2fARB(GL_TEXTURE4_ARB, u8, v1);
			mGL.glVertex2f(w4, y0);
			mGL.glMultiTexCoord2fARB(GL_TEXTURE0_ARB, u1, v1);
			mGL.glMultiTexCoord2fARB(GL_TEXTURE1_ARB, u3, v1);
			mGL.glMultiTexCoord2fARB(GL_TEXTURE2_ARB, u5, v1);
			mGL.glMultiTexCoord2fARB(GL_TEXTURE3_ARB, u7, v1);
			mGL.glMultiTexCoord2fARB(GL_TEXTURE4_ARB, u9, v1);
			mGL.glVertex2f(w4+w8, y0);
			mGL.glMultiTexCoord2fARB(GL_TEXTURE0_ARB, u1, v0);
			mGL.glMultiTexCoord2fARB(GL_TEXTURE1_ARB, u3, v0);
			mGL.glMultiTexCoord2fARB(GL_TEXTURE2_ARB, u5, v0);
			mGL.glMultiTexCoord2fARB(GL_TEXTURE3_ARB, u7, v0);
			mGL.glMultiTexCoord2fARB(GL_TEXTURE4_ARB, u9, v0);
			mGL.glVertex2f(w4+w8, y1);
			mGL.glMultiTexCoord2fARB(GL_TEXTURE0_ARB, u0, v0);
			mGL.glMultiTexCoord2fARB(GL_TEXTURE1_ARB, u2, v0);
			mGL.glMultiTexCoord2fARB(GL_TEXTURE2_ARB, u4, v0);
			mGL.glMultiTexCoord2fARB(GL_TEXTURE3_ARB, u6, v0);
			mGL.glMultiTexCoord2fARB(GL_TEXTURE4_ARB, u8, v0);
			mGL.glVertex2f(w4, y1);
		mGL.glEnd();
		VDASSERT(!mGL.glGetError());

		y += h2;
	}

	mGL.glActiveTextureARB(GL_TEXTURE4_ARB);
	mGL.glDisable(GL_TEXTURE_2D);
	mGL.glActiveTextureARB(GL_TEXTURE3_ARB);
	mGL.glDisable(GL_TEXTURE_2D);
	mGL.glActiveTextureARB(GL_TEXTURE2_ARB);
	mGL.glDisable(GL_TEXTURE_2D);
	mGL.glActiveTextureARB(GL_TEXTURE1_ARB);
	mGL.glDisable(GL_TEXTURE_2D);
	mGL.glActiveTextureARB(GL_TEXTURE0_ARB);

	// readback!
	VDASSERT(!mGL.glGetError());
	mGL.glReadBuffer(GL_BACK);

	if (mGL.EXT_pixel_buffer_object) {
		mGL.glBindBufferARB(GL_PIXEL_PACK_BUFFER_ARB, mGLBuffers[0]);
		mGL.glReadPixels(0, 0, w >> 2, h, GL_BGRA_EXT, GL_UNSIGNED_BYTE, (void *)0);
		mGL.glReadPixels(w >> 2, 0, w >> 3, h >> 1, GL_BGRA_EXT, GL_UNSIGNED_BYTE, (void *)(w * h));
		mGL.glReadPixels(w >> 2, h >> 1, w >> 3, h >> 1, GL_BGRA_EXT, GL_UNSIGNED_BYTE, (void *)(w * h * 5 / 4));
	} else {
		char *dst = (char *)mGLReadBuffer.data();
		mGL.glReadPixels(0, 0, w >> 2, h, GL_BGRA_EXT, GL_UNSIGNED_BYTE, dst);
		mGL.glReadPixels(w >> 2, 0, w >> 3, h >> 1, GL_BGRA_EXT, GL_UNSIGNED_BYTE, dst + (w * h));
		mGL.glReadPixels(w >> 2, h >> 1, w >> 3, h >> 1, GL_BGRA_EXT, GL_UNSIGNED_BYTE, dst + (w * h * 5 / 4));
	}
}

void VDCaptureDriverScreen::ConvertToYUY2_GL_NV1x(int w, int h, float u, float v) {
	// YUY2 conversion - NV_register_combiners path
	mGL.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	mGL.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	mGL.glActiveTextureARB(GL_TEXTURE1_ARB);
	mGL.glEnable(GL_TEXTURE_2D);
	mGL.glBindTexture(GL_TEXTURE_2D, mGLTextures[0]);
	mGL.glActiveTextureARB(GL_TEXTURE0_ARB);
	mGL.glCallList(mGLShaderBase + kVDOpenGLTechIndex_YUY2_NV1x_Y);
	VDASSERT(!mGL.glGetError());

	float y = 0.0f;
	float w2 = (float)w * 0.5f;
	{
		float u0 = -0.5f / (float)mGLTextureW;
		float v0 = 0;
		float u1 = u0 + u;
		float v1 = v0 + v;

		float u2 = +0.5f / (float)mGLTextureW;
		float u3 = u2 + u;

		mGL.glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);

		float y0 = y;
		float y1 = y + (float)h;

		mGL.glColor4f(0.0f, 0.0f, 1.0f, 0.0f);
		mGL.glBegin(GL_QUADS);
			mGL.glMultiTexCoord2fARB(GL_TEXTURE0_ARB, u0, v1);	mGL.glMultiTexCoord2fARB(GL_TEXTURE1_ARB, u2, v1);	mGL.glVertex2f(0.0f, y0);
			mGL.glMultiTexCoord2fARB(GL_TEXTURE0_ARB, u1, v1);	mGL.glMultiTexCoord2fARB(GL_TEXTURE1_ARB, u3, v1);	mGL.glVertex2f(w2, y0);
			mGL.glMultiTexCoord2fARB(GL_TEXTURE0_ARB, u1, v0);	mGL.glMultiTexCoord2fARB(GL_TEXTURE1_ARB, u3, v0);	mGL.glVertex2f(w2, y1);
			mGL.glMultiTexCoord2fARB(GL_TEXTURE0_ARB, u0, v0);	mGL.glMultiTexCoord2fARB(GL_TEXTURE1_ARB, u2, v0);	mGL.glVertex2f(0.0f, y1);
		mGL.glEnd();
		VDASSERT(!mGL.glGetError());
	}

	mGL.glActiveTextureARB(GL_TEXTURE1_ARB);
	mGL.glDisable(GL_TEXTURE_2D);
	mGL.glActiveTextureARB(GL_TEXTURE0_ARB);

	{
		mGL.glCallList(mGLShaderBase + kVDOpenGLTechIndex_YUY2_NV1x_C);

		float u0 = 0;
		float v0 = 0;
		float u1 = u0 + u;
		float v1 = v0 + v;

		mGL.glColorMask(GL_FALSE, GL_TRUE, GL_FALSE, GL_TRUE);

		float y0 = 0;
		float y1 = (float)h;

		mGL.glBegin(GL_QUADS);
			mGL.glTexCoord2f(u0, v1);	mGL.glVertex2f(0, y0);
			mGL.glTexCoord2f(u1, v1);	mGL.glVertex2f(w2, y0);
			mGL.glTexCoord2f(u1, v0);	mGL.glVertex2f(w2, y1);
			mGL.glTexCoord2f(u0, v0);	mGL.glVertex2f(0, y1);
		mGL.glEnd();
		VDASSERT(!mGL.glGetError());
	}

	VDASSERT(!mGL.glGetError());
	mGL.glReadBuffer(GL_BACK);
	if (mGL.EXT_pixel_buffer_object) {
		mGL.glBindBufferARB(GL_PIXEL_PACK_BUFFER_ARB, mGLBuffers[0]);
		mGL.glReadPixels(0, 0, w >> 1, h, GL_BGRA_EXT, GL_UNSIGNED_BYTE, (void *)0);
	} else {
		char *dst = (char *)mGLReadBuffer.data();
		mGL.glReadPixels(0, 0, w >> 1, h, GL_BGRA_EXT, GL_UNSIGNED_BYTE, dst);
	}
}

void VDCaptureDriverScreen::ConvertToYUY2_GL_NV2x_ATIFS(int w, int h, float u, float v, bool atifs) {
	mGL.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	mGL.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	mGL.glActiveTextureARB(GL_TEXTURE3_ARB);
	mGL.glEnable(GL_TEXTURE_2D);
	mGL.glBindTexture(GL_TEXTURE_2D, mGLTextures[0]);
	mGL.glActiveTextureARB(GL_TEXTURE2_ARB);
	mGL.glEnable(GL_TEXTURE_2D);
	mGL.glBindTexture(GL_TEXTURE_2D, mGLTextures[0]);
	mGL.glActiveTextureARB(GL_TEXTURE1_ARB);
	mGL.glEnable(GL_TEXTURE_2D);
	mGL.glBindTexture(GL_TEXTURE_2D, mGLTextures[0]);
	mGL.glActiveTextureARB(GL_TEXTURE0_ARB);
	mGL.glCallList(mGLShaderBase + (atifs ? kVDOpenGLTechIndex_YUY2_ATIFS : kVDOpenGLTechIndex_YUY2_NV2x));
	VDASSERT(!mGL.glGetError());

	float y = 0.0f;
	float w2 = (float)w * 0.5f;

	mGL.glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);

	{
		float u0 = -0.5f / (float)mGLTextureW;		// Y1
		float u1 = +0.5f / (float)mGLTextureW;		// Y2
		float u2 = -1.0f / (float)mGLTextureW;		// chroma left
		float u3 =  0.0f / (float)mGLTextureW;		// chroma right
		float v0 = 0;
		float v1 = v0 + v;

		float y0 = y;
		float y1 = y + (float)h;

		mGL.glBegin(GL_QUADS);
			mGL.glMultiTexCoord2fARB(GL_TEXTURE0_ARB, u0, v1);
			mGL.glMultiTexCoord2fARB(GL_TEXTURE1_ARB, u1, v1);
			mGL.glMultiTexCoord2fARB(GL_TEXTURE2_ARB, u2, v1);
			mGL.glMultiTexCoord2fARB(GL_TEXTURE3_ARB, u3, v1);
			mGL.glVertex2f(0.0f, y0);

			mGL.glMultiTexCoord2fARB(GL_TEXTURE0_ARB, u0+u, v1);
			mGL.glMultiTexCoord2fARB(GL_TEXTURE1_ARB, u1+u, v1);
			mGL.glMultiTexCoord2fARB(GL_TEXTURE2_ARB, u2+u, v1);
			mGL.glMultiTexCoord2fARB(GL_TEXTURE3_ARB, u3+u, v1);
			mGL.glVertex2f(w2, y0);

			mGL.glMultiTexCoord2fARB(GL_TEXTURE0_ARB, u0+u, v0);
			mGL.glMultiTexCoord2fARB(GL_TEXTURE1_ARB, u1+u, v0);
			mGL.glMultiTexCoord2fARB(GL_TEXTURE2_ARB, u2+u, v0);
			mGL.glMultiTexCoord2fARB(GL_TEXTURE3_ARB, u3+u, v0);
			mGL.glVertex2f(w2, y1);

			mGL.glMultiTexCoord2fARB(GL_TEXTURE0_ARB, u0, v0);
			mGL.glMultiTexCoord2fARB(GL_TEXTURE1_ARB, u1, v0);
			mGL.glMultiTexCoord2fARB(GL_TEXTURE2_ARB, u2, v0);
			mGL.glMultiTexCoord2fARB(GL_TEXTURE3_ARB, u3, v0);
			mGL.glVertex2f(0.0f, y1);
		mGL.glEnd();
		VDASSERT(!mGL.glGetError());
	}

	mGL.glActiveTextureARB(GL_TEXTURE3_ARB);
	mGL.glDisable(GL_TEXTURE_2D);
	mGL.glActiveTextureARB(GL_TEXTURE2_ARB);
	mGL.glDisable(GL_TEXTURE_2D);
	mGL.glActiveTextureARB(GL_TEXTURE1_ARB);
	mGL.glDisable(GL_TEXTURE_2D);
	mGL.glActiveTextureARB(GL_TEXTURE0_ARB);

	VDASSERT(!mGL.glGetError());
	mGL.glReadBuffer(GL_BACK);
	if (mGL.EXT_pixel_buffer_object) {
		mGL.glBindBufferARB(GL_PIXEL_PACK_BUFFER_ARB, mGLBuffers[0]);
		mGL.glReadPixels(0, 0, w >> 1, h, GL_BGRA_EXT, GL_UNSIGNED_BYTE, (void *)0);
	} else {
		char *dst = (char *)mGLReadBuffer.data();
		mGL.glReadPixels(0, 0, w >> 1, h, GL_BGRA_EXT, GL_UNSIGNED_BYTE, dst);
	}
}

GLuint VDCaptureDriverScreen::GetOcclusionQueryPixelCountSafe(GLuint query) {
	GLint rv;
	mGL.glFlush();

	int iters = 1000;
	for(;;) {
		mGL.glGetOcclusionQueryivNV(query, GL_PIXEL_COUNT_AVAILABLE_NV, &rv);
		if (rv)
			break;

		if (iters)
			--iters;
		else
			::Sleep(1);
	}

	mGL.glGetOcclusionQueryivNV(query, GL_PIXEL_COUNT_NV, &rv);
	return rv;
}

void VDCaptureDriverScreen::LoadSettings() {
	VDRegistryAppKey key("Capture\\Screen capture");

	mbTrackCursor = key.getBool("Track cursor", mbTrackCursor);
	mbTrackActiveWindow = key.getBool("Track active window", mbTrackActiveWindow);
	mbTrackActiveWindowClient = key.getBool("Track active window client area", mbTrackActiveWindowClient);
	mbDrawMousePointer = key.getBool("Draw mouse pointer", mbDrawMousePointer);
	mbRescaleImage = key.getBool("Rescale image", mbRescaleImage);
	mbOpenGLMode = key.getBool("OpenGL mode", mbOpenGLMode);
	mbRemoveDuplicates = key.getBool("Remove duplicates", mbRemoveDuplicates);
	mRescaleW = key.getInt("Rescale width", mRescaleW);
	mRescaleH = key.getInt("Rescale height", mRescaleH);
	mTrackOffsetX = key.getInt("Position X", mTrackOffsetX);
	mTrackOffsetY = key.getInt("Position Y", mTrackOffsetY);
}

void VDCaptureDriverScreen::SaveSettings() {
	VDRegistryAppKey key("Capture\\Screen capture");

	key.setBool("Track cursor", mbTrackCursor);
	key.setBool("Track active window", mbTrackActiveWindow);
	key.setBool("Track active window client", mbTrackActiveWindowClient);
	key.setBool("Draw mouse pointer", mbDrawMousePointer);
	key.setBool("Rescale image", mbRescaleImage);
	key.setBool("OpenGL mode", mbOpenGLMode);
	key.setBool("Remove duplicates", mbRemoveDuplicates);
	key.setInt("Rescale width", mRescaleW);
	key.setInt("Rescale height", mRescaleH);
	key.setInt("Position X", mTrackOffsetX);
	key.setInt("Position Y", mTrackOffsetY);
}

void VDCaptureDriverScreen::TimerCallback() {
	mbCaptureFramePending = true;
	PostMessage(mhwnd, WM_APP+18, 0, 0);
}

LRESULT CALLBACK VDCaptureDriverScreen::StaticWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
	switch(msg) {
		case WM_NCCREATE:
			SetWindowLongPtr(hwnd, 0, (LONG_PTR)((LPCREATESTRUCT)lParam)->lpCreateParams);
			break;
		case WM_SIZE:
			{
				VDCaptureDriverScreen *pThis = (VDCaptureDriverScreen *)GetWindowLongPtr(hwnd, 0);
				if (pThis->mhwndGLDraw)
					SetWindowPos(pThis->mhwndGLDraw, NULL, 0, 0, LOWORD(lParam), HIWORD(lParam), SWP_NOMOVE|SWP_NOZORDER|SWP_NOACTIVATE);
			}
			break;
		case MM_WIM_DATA:
			{
				VDCaptureDriverScreen *pThis = (VDCaptureDriverScreen *)GetWindowLongPtr(hwnd, 0);

				if (pThis->mpCB) {
					WAVEHDR& hdr = *(WAVEHDR *)lParam;

					if (pThis->mbCapturing) {
						if (pThis->mbAudioCaptureEnabled) {
							try {
								pThis->mpCB->CapProcessData(1, hdr.lpData, hdr.dwBytesRecorded, -1, false, pThis->ComputeGlobalTime());
							} catch(MyError& e) {
								pThis->mCaptureError.TransferFrom(e);
								pThis->CaptureAbort();
							}

							waveInAddBuffer(pThis->mhWaveIn, &hdr, sizeof(WAVEHDR));
						}
					} else if (pThis->mbAudioAnalysisActive) {
						// For some reason this is sometimes called after reset. Don't know why yet.
						if (hdr.dwBytesRecorded) {
							try {
								pThis->mpCB->CapProcessData(-2, hdr.lpData, hdr.dwBytesRecorded, -1, false, 0);
							} catch(const MyError&) {
								// eat the error
							}
						}

						waveInAddBuffer(pThis->mhWaveIn, &hdr, sizeof(WAVEHDR));
					}
				}
			}
			return 0;
		case WM_APP+16:
			{
				VDCaptureDriverScreen *pThis = (VDCaptureDriverScreen *)GetWindowLongPtr(hwnd, 0);
				pThis->SyncCaptureStop();
			}
			return 0;
		case WM_APP+17:
			{
				VDCaptureDriverScreen *pThis = (VDCaptureDriverScreen *)GetWindowLongPtr(hwnd, 0);
				pThis->SyncCaptureAbort();
			}
			return 0;
		case WM_APP+18:
			{
				VDCaptureDriverScreen *pThis = (VDCaptureDriverScreen *)GetWindowLongPtr(hwnd, 0);
				if (pThis->mbCaptureFramePending && pThis->mbCapturing) {
					pThis->mbCaptureFramePending = false;
					pThis->DoFrame();
				}
			}
			return 0;
		case WM_TIMER:
			{
				VDCaptureDriverScreen *pThis = (VDCaptureDriverScreen *)GetWindowLongPtr(hwnd, 0);
				if (!pThis->mbCapturing)
					pThis->DoFrame();
			}
			return 0;
	}

	return DefWindowProc(hwnd, msg, wParam, lParam);
}

LRESULT CALLBACK VDCaptureDriverScreen::StaticWndProcGL(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
	return DefWindowProc(hwnd, msg, wParam, lParam);
}

INT_PTR CALLBACK VDCaptureDriverScreen::VideoSourceDlgProc(HWND hdlg, UINT msg, WPARAM wParam, LPARAM lParam) {
	VDCaptureDriverScreen *pThis = (VDCaptureDriverScreen *)GetWindowLongPtr(hdlg, DWLP_USER);

	switch(msg) {
	case WM_INITDIALOG:
		SetWindowLongPtr(hdlg, DWLP_USER, (LONG_PTR)lParam);
		pThis = (VDCaptureDriverScreen *)lParam;
		CheckDlgButton(hdlg, IDC_POSITION_TRACKMOUSE, pThis->mbTrackCursor);
		CheckDlgButton(hdlg, IDC_POSITION_FIXED, !pThis->mbTrackCursor);

		CheckDlgButton(hdlg, IDC_PANNING_DESKTOP, !pThis->mbTrackActiveWindow);
		CheckDlgButton(hdlg, IDC_PANNING_ACTIVEWINDOW, pThis->mbTrackActiveWindow && !pThis->mbTrackActiveWindowClient);
		CheckDlgButton(hdlg, IDC_PANNING_ACTIVECLIENT, pThis->mbTrackActiveWindow && pThis->mbTrackActiveWindowClient);

		CheckDlgButton(hdlg, IDC_DRAW_CURSOR, pThis->mbDrawMousePointer);
		CheckDlgButton(hdlg, IDC_RESCALE_IMAGE, pThis->mbRescaleImage);
		CheckDlgButton(hdlg, IDC_USE_OPENGL, pThis->mbOpenGLMode);
		CheckDlgButton(hdlg, IDC_REMOVE_DUPES, pThis->mbRemoveDuplicates);
		SetDlgItemInt(hdlg, IDC_WIDTH, pThis->mRescaleW, FALSE);
		SetDlgItemInt(hdlg, IDC_HEIGHT, pThis->mRescaleH, FALSE);
		SetDlgItemInt(hdlg, IDC_POSITION_X, pThis->mTrackOffsetX, TRUE);
		SetDlgItemInt(hdlg, IDC_POSITION_Y, pThis->mTrackOffsetY, TRUE);
reenable:
		{
			bool enableOpenGL = !!IsDlgButtonChecked(hdlg, IDC_USE_OPENGL);
			EnableWindow(GetDlgItem(hdlg, IDC_STATIC_OPENGL), enableOpenGL);
			EnableWindow(GetDlgItem(hdlg, IDC_RESCALE_IMAGE), enableOpenGL);
			EnableWindow(GetDlgItem(hdlg, IDC_REMOVE_DUPES), enableOpenGL);

			bool rescale = enableOpenGL && IsDlgButtonChecked(hdlg, IDC_RESCALE_IMAGE);
			EnableWindow(GetDlgItem(hdlg, IDC_STATIC_WIDTH), rescale);
			EnableWindow(GetDlgItem(hdlg, IDC_STATIC_HEIGHT), rescale);
			EnableWindow(GetDlgItem(hdlg, IDC_WIDTH), rescale);
			EnableWindow(GetDlgItem(hdlg, IDC_HEIGHT), rescale);
		}
		return TRUE;
	case WM_COMMAND:
		switch(LOWORD(wParam)) {
		case IDOK:
			{
				BOOL success;
				UINT w = GetDlgItemInt(hdlg, IDC_WIDTH, &success, FALSE);
				if (w <= 0 || !success) {
					MessageBeep(MB_ICONEXCLAMATION);
					SetFocus(GetDlgItem(hdlg, IDC_WIDTH));
					return TRUE;
				}
				UINT h = GetDlgItemInt(hdlg, IDC_HEIGHT, &success, FALSE);
				if (h <= 0 || !success) {
					MessageBeep(MB_ICONEXCLAMATION);
					SetFocus(GetDlgItem(hdlg, IDC_HEIGHT));
					return TRUE;
				}

				if (!IsDlgButtonChecked(hdlg, IDC_POSITION_TRACKMOUSE)) {
					int x = GetDlgItemInt(hdlg, IDC_POSITION_X, &success, TRUE);
					if (!success) {
						MessageBeep(MB_ICONEXCLAMATION);
						SetFocus(GetDlgItem(hdlg, IDC_POSITION_X));
						return TRUE;
					}

					int y = GetDlgItemInt(hdlg, IDC_POSITION_Y, &success, TRUE);
					if (!success) {
						MessageBeep(MB_ICONEXCLAMATION);
						SetFocus(GetDlgItem(hdlg, IDC_POSITION_X));
						return TRUE;
					}

					pThis->mTrackOffsetX = x;
					pThis->mTrackOffsetY = y;
					pThis->mbTrackCursor = false;
				} else {
					pThis->mbTrackCursor = true;
				}

				pThis->mRescaleW = w;
				pThis->mRescaleH = h;

				if (IsDlgButtonChecked(hdlg, IDC_PANNING_DESKTOP)) {
					pThis->mbTrackActiveWindow = false;
					pThis->mbTrackActiveWindowClient = false;
				} else if (IsDlgButtonChecked(hdlg, IDC_PANNING_ACTIVEWINDOW)) {
					pThis->mbTrackActiveWindow = true;
					pThis->mbTrackActiveWindowClient = false;
				} else if (IsDlgButtonChecked(hdlg, IDC_PANNING_ACTIVECLIENT)) {
					pThis->mbTrackActiveWindow = true;
					pThis->mbTrackActiveWindowClient = true;
				}

				pThis->mbDrawMousePointer = !!IsDlgButtonChecked(hdlg, IDC_DRAW_CURSOR);
				pThis->mbRescaleImage = !!IsDlgButtonChecked(hdlg, IDC_RESCALE_IMAGE);
				pThis->mbOpenGLMode = !!IsDlgButtonChecked(hdlg, IDC_USE_OPENGL);
				pThis->mbRemoveDuplicates = !!IsDlgButtonChecked(hdlg, IDC_REMOVE_DUPES);
			}
			EndDialog(hdlg, TRUE);
			return TRUE;
		case IDCANCEL:
			EndDialog(hdlg, FALSE);
			return TRUE;
		case IDC_USE_OPENGL:
		case IDC_RESCALE_IMAGE:
			if (HIWORD(wParam) == BN_CLICKED)
				goto reenable;
			break;
		}
		break;
	}
	return FALSE;
}

///////////////////////////////////////////////////////////////////////////

class VDCaptureSystemScreen : public IVDCaptureSystem {
public:
	VDCaptureSystemScreen();
	~VDCaptureSystemScreen();

	void EnumerateDrivers();

	int GetDeviceCount();
	const wchar_t *GetDeviceName(int index);

	IVDCaptureDriver *CreateDriver(int deviceIndex);
};

IVDCaptureSystem *VDCreateCaptureSystemScreen() {
	return new VDCaptureSystemScreen;
}

VDCaptureSystemScreen::VDCaptureSystemScreen()
{
}

VDCaptureSystemScreen::~VDCaptureSystemScreen() {
}

void VDCaptureSystemScreen::EnumerateDrivers() {
}

int VDCaptureSystemScreen::GetDeviceCount() {
	return 1;
}

const wchar_t *VDCaptureSystemScreen::GetDeviceName(int index) {
	return !index ? L"Screen capture" : NULL;
}

IVDCaptureDriver *VDCaptureSystemScreen::CreateDriver(int index) {
	if (index)
		return NULL;

	return new VDCaptureDriverScreen();
}
